JavaScript是一种非类型的(?),松类型,弱类型(?),动态类型。脚本语言。 并基于原型的继承,是一种真正的OO语言。非惰性求值。语言不同分类间相互衍生的产物:同时是说明式和命令式,并同时具有函数式特性,命令式(基于原型的面向对象和过程)的特性弱类型,表明该语言在表达式运算中不强制校验运算元的数据类型,不表明语言是否具有类型系统。

1.赋值是一个运算,而不是一个语句。
2.赋值运算的本质:修改存储单元中的值
3.JavaScript 中只有一种方法来完成函数调用,即在函数后紧临函数调用运算符()
4.引用,在JavaScript中只是表示传递给函数的是对象或数组的引用(地址),而不是对象自身。但是在函数内部中改变的数值不会影响函数传入的参数本身。
5.JavaScript中字符串无法修改,通过传值来比较
6.var声明的变量是永久性的,用delete删除会引发错误
7.没有块级作用域。函数中声明的所有变量在整个函数体内都有定义。
8.将所有的变量声明在函数开头。
9.全局变量是全局对象的属性;局部变量是一个特殊的调用对象的属性
10.作用域链 (scope chain) <=事件冒泡?
查询某个变量的值时触发变量名解析(variable name resolution)
顶层代码 scope chain=全局变量
函数(不含嵌套) scope chain=调用对象+全局变量
嵌套函数 scope chain=3+
11.使用了GC,垃圾收集机制
12.delete影响属性值,并不影响这些属性引用的对象。
13.void总是舍弃运算数的值然后返回undefined
14.switch匹配的case是用===完全等同运算符判定的,而不是用==相等运算符判定
15.函数function定义在解析时发生,而不是在运行时发生
16.with语句用于暂时修改作用域链 (速度慢,难优化)

function
了解 JavaScript Function 的一切了。Function 的本质是什么,什么是 closure,如何用 Function.prototype.apply 改变 this,为什么要改变 prototype 引用。如果这些问题有一个你搞不明白,你都写不出 Function.prototype.bind。

1
2
3
4
5
6
7
8
Function.prototype.bind = function(){  
var fn = this,
args = Array.prototype.slice.call(arguments),
object = args.shift();
return function(){
return fn.apply(object, args.concat(Array.prototype.slice.call(arguments)));
};
};

JavaScript 中只有一种方法来完成函数调用,即在函数后紧临函数调用运算符()
匿名函数调用
(function() {}()); // 返回调用结果
(function() {})(); // 返回函数自身
void function(){} (); // 调用函数并忽略返回值
永远不可能在JavaScript中通过修改参数来影响到函数外部的变量
动态方法调用
要将一个普通函数作为一个对象的方法调用,或者将A对象的方法作为B对象的方法调用,唯一要做的,也仅是改变一下this引用。
call 和 apply
Area.prototype.doCalc = function( ) {
calc_area.apply(this, arguments); //calc_area非Area方法
}

Scope
没有块级作用域。函数中声明的所有变量在整个函数体内都有定义。
建议将所有的变量声明在函数开头。
全局变量是全局对象的属性;局部变量是一个特殊的调用对象的属性
变量作用域:代码执行阶段对变量存储的理解。只有表达式,局部和全部3种作用域。生存周期只有2个:函数内的局部执行期间和函数外引擎的全局执行期间。将对象(类)的继承关系,与对象(类)的行为描述分离,只能依赖“变量作用域”来实现OO的封装特性
语法作用域:语法分析阶段对代码块组织结构的理解。互不相交,可以存在平行或包含关系。高级别可以嵌套低级别的语法作用域,反之不成立;高级别的流程变更子句(或语句)可以跨越低级别的作用域:全局 > 函数 > 批语句 > 语句 > 表达式。

Closure
函数闭包:函数代码在运行过程中的一个动态环境。
对象闭包:with语句实现
闭包是运行期概念。
闭包是对应于运行期的函数实例的,而不是对应函数(代码块)的。
每一个函数实例都有一份
eval()
不同JavaScript引擎对eval()所使用的闭包环境的理解并不相同。
一般来说,eval()总是使用当前函数的闭包。
JavaScript的执行 动态执行 -> 不能真实编译的,指编译成为二进制
代码文本先被解释为语法树,然后按照语法树来执行的
每次执行语法树中的一个函数(实例)时,会复制一个结构scriptObject。包含函数相关的形式参数,函数局部变量,upvalue,内层函数嵌套信息
scriptObject动态关联到一个闭包,闭包与scriptObject具有不同的生存周期。
按语法树执行函数中的代码,当访问变量时,先查scriptObject中的局部变量,最后查upvalue
函数在定义它的作用域内被执行,而不是在调用它的作用域里被执行。嵌套函数可以访问包含函数的所有参数和局部变量。

1
2
3
4
5
6
7
8
(function() { 
// code goes here.
})();

uniqueID = ( function() {
var id = 0;
return function() { id++; };
} )();

理解变量作用域是理解闭包的第一步

arguments
是一个类似数组的对象,并非真正的数组。
只是一个普通的js标识符,不是保留字。
一旦函数内有变量名为arguments则会隐藏对arguments对象的引用
作用:

1. 检测是否使用了正确的参数个数
2. 能写可变参数函数 

arguments改变也会影响传入的参数
arguments.callee
属性可以让匿名函数递归调用自身

1
2
3
4
function(x) {
if (x<=1) return 1;
return x * arguments.callee(x-1);
}

this
this作为函数内部的一个对象。谁调用函数,this就指向调用者本身。
由于late-banding导致this的指向具有迷惑性。
任何用作方法(obj.func = method)的函数都被有效地传递了一个隐式的参数this;不允许为this赋值

OO
JavaScript不会有一个正式的类名字,它只是通过构造函数及其原型对象来近似地模拟类。
所有的函数都有一个prototype属性。js对象从它的原型继续属性。
使用原型对象可以显著减少每个对象所需的内存数量。
对象实例初始化:
通过在构造器中利用this引用来初始化
通过构造原型实例来初始化
通过Object.create()并使用属性描述符的方式来构建对象并初始化
实例属性,是由构造函数创建和初始化的属性
每个实例方法由一个类的所有实例来共享。必须为属性显式指定 this 关键字
类属性,类的每个属性都只有一份拷贝。全局的。Rectangle.UNIT = new Rectangle(1,1);
类方法,通过类自身调用,非类的一个具体实例调用,不使用this。也是全局的。 Date.parse()
空对象 null 属于对象类型,对象是空值
空的对象 obj=new Object(); obj={ };
直接量 Literals(undefined, null, true/false, 数值,字符串,正则)的声明中不包含运算过程;
初始器 Initialiser 的声明中是可以包括运算过程的。
取一个不存在的属性的值不会导致异常,而是返回undefined
或者通过 if(typeof (obj.prop) != ‘undefined’) 来判断 (还是会存在obj.prop=’undefined’ 特例
或者通过 obj instanceof myobject
propertyIsEnumerable() 被标准强制实现成“只检测对象的非(自原型链继承而来的)继承属性”

namespace
新建一个空对象作为名称空间
var Class = {};
Class.define = …
一个模块不应该为全局名称空间添加多于一条的标记
使用个人域名作为名称空间 com.chaindomain.Class
var com;
if (!com) com={};
else if (typeof com != ‘object’)
throw new Error(“com already exists and is not an object.”);
名称空间导入标记
var defineClass = com.chaindomain.define; //避免了冗长的模块名称
或在模块内部继续
var Class = {};
Class.define = com.chaindomain.define;

delete
不能delete var 声明的变量
不能delete 直接继承自原型的成员。会恢复到prototype的值
仅在删除一个不能删除的成员时,才会返回false
delete book.chapter2 不仅置undefined和null,而且移除属性,in也不会检测到了。for..in..

对象和数组

对象 {}
对象的另一种解释是:一个无序的属性集合。
通常用var来声明变量,但在声明对象的属性却不能使用。 book.title=”The Guide of JavaScripte”;
!== 和 != 的区别在于undefined 和 null

obj.property 和 obj[‘property’] 的区别
后者更灵活。for (var i=0; i < 4; i++) { addr += obj[‘property’ + i]; }

constructor属性可确定一个对象的类型
d.constructor == Date 也可以写成 d instanceof Date
toString() toLocaleString()
valueOf()
hasOwnProperty() 非继承的自有属性
propertyIsEnumerable() 非继承的自有属性,且此属性可以在for-in中被枚举
isPrototypeOf() 判断对象是否是参数的原型对象

数组 []
数组不过是一个有额外功能层的对象
创建数组

  1. var arr = [1,2,3];
  2. var arr = new Array();
  3. var arr = new Array(1,2,3);
  4. var arr = new Array(10);
    index 必须是0~2^32-1的整数,不然会变成生成对象的新属性
    length 总是比数组最大元素的数大1
    删除数组
    Array.shift()
    Array.pop()
    Array.splice()
    遍历数组
    for (var i=0; i < arr.length; i++) { … } // 前提 arr 是连续数组
    push() pop()
    unshift() shift()

函数

函数最重要的特性是他们可以被定义和调用。
最后return可有可无。无,相当于return undefined
用 typeof 进行类型检测 a = a || []
arguments对象是一个类似数组的对象,并非真正的数组.只是一个普通的js标识符,不是保留字。一旦函数内有变量名为arguments则会隐藏对arguments对象的引用
作用:

1. 检测是否使用了正确的参数个数
2. 能写可变参数函数 

arguments改变也会影响传入的参数
arguments.callee属性可以让匿名函数递归调用自身
function(x) {
if (x<=1) return 1;
return x * arguments.callee(x-1);
}

如果一个函数参数过多,可以通过在函数外wrapper一层函数,然后传对象的方式传递
easycopy ({from:a, to:b, length:4});
任何用作方法(obj.func = method)的函数都被有效地传递了一个隐式的参数this;不允许为this赋值

prototype属性
引用的是预定义的原型对象。

call() => f.call(o, 1,2);
apply() 与call() 相似,只是传递的参数由数组组成 f.apply(o, [1,2]);
第一个参数都是要调用的函数的对象

构造函数和原型对象

自定义构造函数
function Rectangle(w,h) {
this.width = w;
this.height = h;

this.area = function() { return this.width * this.height; }  // 这样效率很低 why? 因为每份实例都要定义自己的area方法呀。使用原型对象可以显著减少每个对象所需的内存数量。
Rectange.prototype.area = function() { ... } 

}

所有的函数都有一个prototype属性。js对象从它的原型继续属性。
使用原型对象可以显著减少每个对象所需的内存数量。
在对象创建以后才添加到原型中的属性,对象也可以继承。
prototype属性是由一个类的所有对象共享的属性,prototype是理想的定义方法的工具;同样,常量也适用prototype

JavaScript不会有一个正式的类名字,它只是通过构造函数及其原型对象来近似地模拟类
实例属性,是由构造函数创建和初始化的属性
每个实例方法由一个类的所有实例来共享。必须为属性显式指定 this 关键字
类属性,类的每个属性都只有一份拷贝。全局的。Rectangle.UNIT = new Rectangle(1,1);
类方法,通过类自身调用,非类的一个具体实例调用,不使用this。也是全局的。 Date.parse()

比较方法 a.compareTo(b)
按地址比较对象,而不是按值.

超类和子类
显示调用超类的构造函数,叫做构造函数链

Rectangle.call(this, w, h); 可以简化成

PositionedRectangle.prototype.superClass = Rectangle;
this.Rectangle(w, h);
超类构造函数通过this对象被显式调用,就不再要使用call或apply。但要注意子类的子类情况。也就是说superclass属性只能在继承层次中用1次。

混入类 mixin class
只定义其他类可以借用的有用方法,除此以外什么也没做。

判定对象类型
typeof 注意:typeof null == typeof undefined == ‘object’
instanceof o instanceof object || function 总是true
toString() 只对内建对象类型有效,那么 Object.prototype.toString.apply(o);

名称空间和初始化

新建一个空对象作为名称空间

var Class = {};
Class.define = …

一个模块不应该为全局名称空间添加多于一条的标记

那如果名称冲突的js文件,通过在不同子目录下的存放动作是否可以避免冲突了?
这也就是为何提成js模块化编程

使用个人域名作为名称空间 com.chaindomain.Class

var com;
if (!com) com={};
else if (typeof com != ‘object’)
throw new Error(“com already exists and is not an object.”);

名称空间导入标记
var defineClass = com.chaindomain.define; //避免了冗长的模块名称

在模块内部继续
var Class = {};
Class.define = com.chaindomain.define;

闭包定义私有空间
语法错误:
语法分析通不过,整个脚本代码块都不执行。
语法分析通过后,但在执行过程中出错,那么在同一代码上下文中,出错点后的代码将不再执行。

全局变量:在函数外声明的变量
局部变量:在函数或子函数内声明的变量。

通过 typeof 获得对象6种类型(first-class)之一:undefined, number, string, boolean | function, object

显式声明:var
隐式声明:不用var

=== 如果不是同一个变量或其引用,则2个变量不相等,也不相同。

finally { … } 总是在try/catch块退出之前被执行,但“不一定”能被完整地执行(在finally里面又发生异常或者throw)

二义性

  • 如果表达式中存在字符串,则优先按字符串连接进行运算
    () 虚拟参数表
    传值参数表
    充当if, while和do…while语句中的词法元素时,()会有”将表达式结果转换为布尔值”的副作用
    强制表达式运算
    [ ] 可以理解为数组声明或下标存取
    可以理解为对象成员存取

语法作用域:语法分析阶段对代码块组织结构的理解
互不相交,可以存在平行或包含关系。高级别可以嵌套低级别的语法作用域,反之不成立;高级别的流程变更子句(或语句)可以跨越低级别的作用域
全局 > 函数 > 批语句 > 语句 > 表达式

变量作用域:代码执行阶段对变量存储的理解。只有表达式,局部和全部3种作用域。生存周期只有2个:函数内的局部执行期间和函数外引擎的全局执行期间。

空对象 null 属于对象类型,对象是空值
空的对象 obj=new Object(); obj={ };
属性<构造器>.prototype指向原型。对象只有“构造自某个原型”的问题,并不存在“持有某个原型”的问题
原型也是对象实例

将对象(类)的继承关系,与对象(类)的行为描述分离
只能依赖“变量作用域”来实现OO的封装特性

函数式语言 (连续求值)
这里的函数,指的是:函数 == “lambda”
+函数是运算元
+函数内保存数据
JavaScript中的函数作为参数时,也是传递引用的。但没有地址概念
函数语言可以在函数内保存数据。在命令式语言中,函数内部的私有变量(局部变量)是不能被保存的
+函数内的运算对函数外无副作用
函数入口参数作为值参数,而不修改它
在运算过程中不会修改函数外部其他数据的值(如全局函数)
运算结束后通过函数返回向外部系统传值
永远不可能在JavaScript中通过修改参数来影响到函数外部的变量

包装类
在元数据经过“包装类”包装后得到的对象,与原来的元数据并不再是同一数据,只是二者的值相等而已。

参考书目