c#和TypeScript(javascript)的风格区别

总是要在两者间切换,做一下笔记:

1. 变量和参数的类型,C#是类型在前,变量名在后;TS是变量名在前,类型在后。
2.用花括号表达式new一个对象时,设属性值, C#用等号,TS用冒号。有一点是相同的,不用指定属性类型(c#如果是匿名类对象会自动推导出类型,JS本来就可以根据表达式推导出类型)

3.Optional parameter, c#的函数里的可选参数只能在最后,用等号给出默认值。而TS里分裂出两种,一个是Optional parameter,可选参数,参数名后加问号,也是只能用在最后,不传值的时候就是undefined(不能指定默认值, 简单化为undefined,有点类似nullable, 但是undefined和null是两个类型(值),虽然都可以转成bool类型);另一种是default-initialized parameter, 这种可以指定默认值,用等号给出,不一定要放在最后,如果放在前面的时候希望使用预定义的默认值,必须传undefined来取到(这个设计有点多余,我直接传默认值不就行了,传个undefined还要让别人推测函数定义的参数默认值是什么,而参数默认值就不应该在发布后改变,所谓的灵活没有意义),但如果是放在必须参数的后面,则也是可选的,会变成带默认值的可选参数(和c#一样)。而对于Javascript(最宽松),所有参数都是可选的,不传的时候就是undefined, 从ES6/ES2015起,javascript也支持参数带默认值(等号)。

JavaScript语言精髓与编程实践 阅读笔记

2. ==(相等,equal)和===(全等,strictly equal)的差别:
2a.因为JS可以自动做类型转换,所以1==”1″ 但是1 !== “1”, 所以对两个值类型之间的比较,相等只要转换后一样就行。全等则必须类型也相同。
        2b.如果是两者都是引用类型,他们之间的比较不会自动转换,则相等就代表了引用对象地址相同,类型也相同(必须都是引用),所以也必然全等。
        2c.如果一个值类型和一个引用类型之间比较是否相等(==),会自动把引用先转成值类型再比较值。全等根据定义必然为false
        2d. 还有, null(对象/函数,引用类型) == undefined(自成一类的值类型)。这个也是可以自动转换的。
3. 引用类型和值类型的变量的例子,https://jsfiddle.net/yww325/Lg7axgnc/ 这两变量(后一个是字符串对象)一个可以修改属性一个不行。JS中字符串是值类型,这点和C#不同,c#中字符串是不可变的引用类型(c#也有点值类型的意思,因为不可变赋值后指向的是新的字符串)。
var str = ‘abcde’;
var obj = new String(str);
但是,在赋值运算时,虽然字符串是值类型,JavaScript宿主解释程序却不会直接修改变量指向的内存地址,而是变成拷贝一个新字符串的地址过来。这又和引用类型是一样的,而且JavaScript中字符串类型也是一旦生成,就不能修改其中的字符的“不可变”类型。所以所谓的值类型仅仅是在上面的代码里会体现出无法修改属性的特点而已。

4. 正则表达式老生常谈:RegExp也是一种类型,表达式直接量格式是/pattern/modifiers , 参见: https://www.w3schools.com/Jsref/jsref_obj_regexp.asp, 也可以用new RegExp(‘pattern’,’modifiers ‘);来创建

5.神奇的成员访问运算符[]:1.5.3. 属性存取与方法调用 中提到:JavaScript 并不这样,它是通过“ 运算” 来实现面向对象特性的典型。例如我们在这一小节要说到的属性存取与方法调用…… “ .”与“ [ ]”是两个运算符。…  具体到语法的实现方法上,只需要使得“ .” 和“ [ ]” 运算的优先级高于“ ( )” 即可 。把它们看成用于寻找定位对象成员的运算符,这就是Javascript精妙的地方。不需要对象指针,函数指针,虚函数,虚表这些东西在实现机制上的帮助,”[]”实现成运算符,且能接受变量、直接量或表达式作为运算元后(而不仅仅是.能接受的标识符),即使Obj[‘methodA’]()这样的写法,已经足够定位到成员并且作为函数运行之。这就是解释型语言的优势。C++中”[]”运算符却不是用来做member access的。http://www.cplusplus.com/doc/tutorial/operators/ . 这样也就很好理解JavaScript的数组类型了,数组类型就是数字(下标)作为成员名的可增长的对象类型(JS六大类型中没有数组和正则表达式,他们都是object的一种,和String类一样的地位),所以JavaScript不存在连续存放的多维数组,“多维数组”只是某个位置的成员又是一个数组类型而已。
6. Javascript中的可比较序列的类型(可以应用< <=, > >=)有:数字,布尔,和字符串,由于可以类型转换,字符串除非和字符串比的时候是按字符一个个比,和其他类型比都是转成数字来比(布尔也是数字),不能转成数字的串会被转成NaN。任何东西和undefined或者引用类型比都是返回false.NaN和任何数字比也返回false
7. GOTO 语句没有被启用, 所以Label 只能用于 break 和 continue 关键字之后。 不带标签的 break” 仅能用于循

环(和 switch分支)的内部并且只退出最内层,带了标签的break可以用于该标签语句的block内的break(这个break必须用label指明跳出范围):

my_label: {
if (str && str.length < 10) {
break my_label;
}
str = str.substr(str.length-10);
}

因为没有goto, Label除了用于block语句开始,其他就只有用于循环和swtich语句开始时有用了,否则写了label也没有地方能跳转过来。如果把循环和switch的block都看成block,那么可以说label就是在各种block开始处才有用。

8. 没有引用的匿名函数的调用方法(一次过的调用):

// 示例3. 没有引用的匿名函数的调用方法(1)
(function() {
// …
}());
// 示例4. 没有引用的匿名函数的调用方法(2)
(function() {
// …
})();
// 示例5. 没有引用的匿名函数的调用方法(3)
void function() {
// …
}();

感觉用void把它当作表达式运算而不保留值最直观,没有额外的括号需要数是不是匹配。但平时代码中可能还是示例4看见得比较多些。直接function(){…}()不行是因为()前面的部分已经被解释成函数直接量语句了,后面就是一个空的括号语句(),JS支持没有分号的写法,解释器尝试完所有的解释得到了这种结果。但是作者是声称的空括号语句不行,实际上一个函数直接量做语句也不行,比如(1,2);是合法的语句,但function(){…}(1,2);后面的语句跑不到,说明是前面的function声明被当作语句是不行的(加个括号变表达式就不会报错,再加函数运算符括号对才会被执行)。
9 又一个神奇的运算符new, Javascript将 new 右边的运算元视为构造器, 传入代表新对象的this指针(或者我们叫this对象引用比较好),构造器可以是函数,内部可以修改this引用对象的属性;也可以不对this对象做任何事情返回一个新对象,只是new 运算符浪费掉了;构造器也可以是表达式: https://jsfiddle.net/yww325/cty1oyLh/ . 但后两个都不是构造器的主(zheng)流(que)用法(没有利用new来的this引用)。
10 in运算符: 检查某对象是否定义了某属性的最佳方法还是用引擎(host)支持的in运算符,如果老引擎(如IE6)不支持in,则无法区分代码中没有定义过Obj.PropertyA以及Obj.PropertyA=undefined这两种情况。另外原书中似乎有一个错误,也有人也发现了问题:  https://segmentfault.com/q/1010000008777448/a-1020000010869793
11. 循环列举 for…in 语句(和in 运算符是两回事,虽然都和检测属性相关).
       11a. constructor, 数组对象的length属性等这些内置属性in运算符返回true,但是循环列举 for…in 语句循环不出来.
       11b.for…in 语句字面上要求属性是enumerable的,和 obj.propertyIsEnumerable()=true|false的结果本来应该一致。但是for…in语句可以列出继承的父类(通过prototype)上定义的属性,但是ECMAScript规范要求propertyIsEnumerable方法只检测对象的非继承属]性。这就比for…in的少了,有in运算符的只要包括propertyName in this.constructor.prototype就行了,老引擎实现propertyIsEnumerable时可以用this[propertyName] !== this.constructor.prototype[propertyName]来剔除掉继承来的引用属性(全等),但是如果是值类型属性呢?似乎应该检查constructor.prototype[propertyName]==undefined更准确,但是也没法区分值就是undefined的值属性(如果没有in运算符)。
12. 解释器对语句优先:
12a:

var code = ‘if (true) { entry : 1 }’;  //entry is label
var value = eval(code);
alert( value ); // 显示值1

12b: var a = 1, 2, 3; // a并不等于3,没有解释成序列表达式,var声明会使连续运算表达式变为连续声明语句(优先)

13.  命令式语言的抽象层次不断提高:从命令式起步,面向对象是命令式语言的扩展, 最终就是“接口首次从系统或模块中剥离了“ 数据”的概念,进而把与数据有关的关系也清理了出去——例如引用(对象间的引用是面向对象体系的灾难之源)”  。 接口基于的原则并不是“ 结构确定,则算法确定”,而是“ 在公同的规约描述下的(算法的)功能,是确定的”。所以我认为,接口中的方法最好不要引用任何类,接口中的方法的参数也应该是接口,把对象(类)的使用清理出去

而 JavaScript则支持三种编程范型:函数式、命令式和(基于原型的)面向对象。(虽然没有使用接口的概念,但是可以用类对象来当接口用)

14. 复合语句是大括号{}括起来可以和普通语句替换的地方;批语句则是必须要大括号,不能用一句语句的地方,比如try, swtich. 语句里不能嵌函数声明(如果语句包含了函数声明,引擎会当作函数是在语句之后紧跟着声明的),但是可以用函数表达式或者函数执行语句。
15.  SpiderMonkey里引入的let就是在Block级别的变量作用域限定符,block级别包括复合语句和批语句,以及函数声明等各种大括号。
16. JavaScript 环境中开发一些大型项目时,“ 随时随地”都可以从函数内泄露到全局的命名(函数内忘了写var),以及随在函数内对全局变量可能发生的修改,总在威胁着项目的品质。
17. 浏览器环境中,全局变量如果用了var声明,就delete不掉,只能delete 没有var的全局变量,因为没有var的全局变量相当于window对象的property,delete仅仅是用来删property的https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete
18. 因为在 JavaScript 中,语法解释与执行分成两个阶段,而变量声明是在语法解释阶段处理的,因此在代码执行之前,这个变量就已经在它的语法作用域在存在了。这带来两点影响:
18a.同一个变量多次用var重复声明并不违法,不声明直接用也可以被引擎理解(临时创建成undefined, 而如果有var则在执行开始前,解释阶段后就已经创建成undefined,总之,不管有没有var,第一次创建都是成undefined)。
        18b. 函数声明也是在解释阶段就注入了。所以如:

TMyClass = Class(TObject, _ConstructorName(foo));
function foo() {
// …
this.Class = TMyClass;
}

在这个例子中,由于 foo 是一个函数声明的标识符,所以在执行期的任意
位置都可以直接使用。在顺序上,使用 foo 这个标识可以早于该标识符的声明
这使得 JavaScript不必使用专门的语法来处理前置声明(例如 pascal语法中的
forword) 。同样的原因,在 JavaScript 中,对象可以轻松地持有它自身,或者
它的类——只要它们在可视的局部或全局范围内,被显式地声明过。

19  “构造器的prototype 属性总是来自于 new Object()产生的实例” 这是说new运算符后面的构造器不但产生了Object实例,而且也对prototype的属性做了访问,设置了新对象的prototype.constructor为Object函数(或别的什么函数,对应的也就是产生了别的什么函数的对象实例)。JavaScript 中的对象实例本质上只是“ 一个指向其原型函数(prototype)的,并持有一个成员列表的结构” , 任意对象的prototype属性就是一个普通的“空的对象”, 其constructor属性被设过,所以任意对象实际上含着这样一个子对象+自己的成员列表结构(经常在prototype函数里被初始化,但是也可以任意时刻追加或者删除)
var __proto__ = null;
function get_prototype() {
if (!_proto_) {
__proto__ = new Object();
__proto__.constructor = this; //this 是Object()函数或自定义的任何MyObject()函数
}
return _proto_;
}

20. 普通的“空的对象”一点也不空,本身有很多Object.prototype.内置方法,比如valueOf(),hasOwnProperty()等可以被子对象调用。和Object.getOwnPropertyNames(), getOwnPropertyDescriptor()等一堆Object的静态方法要互相区分。

21. 函数(通过function关键字声明或者new Function()构造的变量)本身不作为构造器而是作为一个对象来看的时候,它比“空的对象”也就是常规Object对象要更进一步,它的prototype多加了bind(), call()等内置方法,而且还有如arguments和name这些属性

22. Javascript中通过一个普通函数来创建它的对象实例的几种方法:

  22a. new运算符,这也是最主流的方式。 另外如果对该函数的prototype属性两个操作(1,(可以通过Object.create)设置prototye对象为基类对象,2.设置prototype.constructor为当前函数(子类)并且函数中要call或者apply基类方法,即可实现真正的面向对象的类继承(class inheritance)模式,甚至多重继承。参见https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create (这个例子里用了Object.create,实际上继承的实现也可以用new)。 虽然这里并没有class的概念,但用new Rectangle是模拟出class的pattern来了。
  22b. Module pattern,  返回空的对象{}基础上自己加属性,没用到function的特性,也能实现简单的继承(但是instanceof运算符和prototype.isPrototypeOf()方法就没什么用了),参见:http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html
 22c. Object.create + this.apply 方法, 参见: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply , 这个方法不用new,不关注prototype.constructor。而且也能实现类继承模式。参见:https://jsfiddle.net/yww325/gwmvsjLc/

函数式编程(fp)的数学推导

这个知乎问题的回答中, wsivoky 写了几个基本的函数:

用 javascript (你也可以把它当作函数式语言)就是这样:

function one (a) {
return function (b) {
return a(b);
}
}
.....
用 javascript 就是这样:
function succ (a) {
  return function (b) {
    return function(c) {
      return b( a(b)(c) );
   }
 }
}
以及two的定义:
function two(a) {
return function (b) {
return succ(one)(a)(b);
}
}这里面的推导感觉就像数学公式的推导。我自己整理了一下思路,算是做个笔记吧。
用第二个字代表下标,以区分不同函数(或者说公式)中的变量,一次(这里刚好调用每次一个参数)函数调用fn()代表封装返回一次函数(return in function),函数本身定义就已经有了一次封装,内部如果再定义一个函数就再加一次参数,这里假定所有函数都需要参数,所以one函数接受的参数函数自己也有参数:
one(a1) = a1(b1) , 对外两次参数(包两层),实际调用格式要写成return one(a)(b)
succ(as) = bs(as(bs)(cs)), 对外三次参数(包三层), 下面这行就是它的调用格式return succ(a)(b)(c)
two(a2)=succ(one)(a2)(b2), 对外两次参数(包两层),格式return two(a)(b),one不是需要传递进来的参数,不是封装的结果

下面来解析上面最后一行的two公式(或者说two函数)如下,把公式解析到最底层的abc基本函数的调用,把succ和one解析掉:
two=succ(one)中把one公式应用(代入)到succ公式的as里,即消掉了参数as函数,同时也去掉as变量的引用,as既是函数,也是变量。
succ(one)变身 = bs(<a1(b1)>(bs)(cs)) 尖括号表示仅替代了函数变量as,参数还需要再替换
这时因为参数bs是给来替换的One函数的参数,也就是One函数的第一次参数,可以替换a1, 替换后=bs(<bs(b1)>(cs)), 
这时参数cs是给One公式代入后运行得到的函数再运行需要的那个参数,即One公式的第二次参数,所以可以用来代替b1的,所以
two可以写成two = succ(one) = bs(bs(cs)),这个新公式作为two的函数体解析后的写法,和上面老的two公式是等价的。这个新的两次参数也和老two公式的两次参数(不是两个参数,是两次)匹配
所以two把第一次参数a2代入后替换掉bs变量,
two=a2(a2(cs)),
再把第二次的参数b2代入cs,得到two = a2(a2(b2)),不加下标的通用写法就是two=a(a(b)),为了解析还是保留下标的好。

依次类推,如果说three = succ(two),函数体省略,两个参数:第一个是函数参数,第二个是给前一个用的参数, 那么还是同理把two公式代入到succ(as)公式,得到
three=succ(two) = bs(<a2(a2(b2))>(bs)(cs)), 再替换参数得到 three = bs(bs(bs(cs))),重新编组参数得到的通用写法就是
three = a(a(a(b))
加下标区分,应该写成three = a3(a3(a3(b3))