作用域
- 作用域是一套用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标示符名称进性变量查找的规则。
- 引擎会为出现在赋值操作左侧的变量进行LHS查询,其余的执行RHS查询。当执行LHS查询时,如果直到在全局作用域也找不到目标变量,全局作用域酒会创建一个具有该名称的变量;当执行RHS查询时,如果直到在全局作用域也找不到目标变量的话,就会抛出ReferenceError异常。如果RHS查询找到一个变量,但是你尝试对这个变量的值进行不合理的操作,那么引擎会抛出一个TypeError异常。ReferenceError同作用域判别失败相关,而TypeError则代表作用域判别成功了,但是对结果的操作是非法的。
函数作用域和块作用域
函数作用域
- 函数作用域是指属于这个函数的全部变量都可以在整个函数的范围内使用及复用。
- 如果function是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。
提升
- 函数会首先被提升,然后才是变量。
- 函数声明会被提升,但是函数表达式却不会被提升。如果在函数表达式所在的代码块前调用该函数,会抛出TypeError异常。如:
foo();
var foo=function bar(){
};
- 重复的var声明会被忽略掉,但出现在后面的函数声明可以覆盖前面的。
闭包
- 当函数可以记住并访问所在的词法作用域时,就产生了闭包。
- 模块模式需要具备两个必要条件:
1.必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
2.封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或或者修改私有的状态。
关于this
- this并不指向函数本身。
- this不指向函数的作用域。(作用域“对象”无法通过JavaScript代码访问,它存在于Javascript引擎内部)。
- this的值取决于函数的调用方式。
- 不存在所谓的“构造函数”,只有对函数的构造调用。
- this的绑定规则;
1.默认绑定。独立函数调用,不带任何修饰的函数引用进行调用,默认绑定到全局对象,如果使用严格模式,this会绑定到undefined。
2.隐式绑定。调用位置有上下文,即该函数被某个对象拥有或者包含,this绑定为该对象。
3.显式绑定。’用过call()和apply()方法直接指定this的值。
4.new绑定。只用new来调用函数,this绑定为该函数返回的对象。 - this绑定的优先级。显式绑定优先级比隐式高,new绑定最高。
- 当null或者undefined作为this的绑定对象传入call、apply或者bind,这些值会被忽略,实际应用的是默认绑定规则。
对象
类型
- typeof null时会返回字符串“object”。原理是这样的,不同的对象在底层都表示为二进制,在JavaScript中二进制前三位都为0的话会被判为object类型,null的二进制全是0,自然前三位也是0,所以执行typeof时会返回“object”。
内置对象
- null和undefined没有对应的构造形式,只有文字形式。相反,Date只有构造,没有文字形式。
属性描述符
- 可以通过Object.defineProperty()来添加一个新属性。如:
var myObject={};
Object.defineProperty(myObject,"a",{
value:2,
writable:true,
configurable:true,
enumerable:true
});
不变性
- 对象常量 结合writable:false和configurable:fasle就可以创建一个真正的常量属性。
- 禁止扩展 如果想禁止一个对象添加新属性并且保留已有属性,可以使用Object.preventExtensions();
- 密封 Object.seal()会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用Object.preventExtensions()并把所有现有属性标记为configurable:false。
- 冻结 Object.freeze()会创建一个冻结对象,这个方法实际上会在一个现有对象上调用Object.seal()并把所有“数据访问”属性标记为writable:false,这样就无法修改它们的值了。
Getter和Setter
- getter和setter是隐藏函数,作用在属性上。在ES5中可以用它们部分改写默认操作。
- 当你给一个属性定义getter、setter或者两个都有时,这个属性会被定义为“访问描述符”,JavaScript会忽略它们的value和writable特性,取而代之的是关心set和get特性。
存在性
- in操作符会检查属性是否在对象及器原型链中。相比之下,hasOwnProperty()智慧检查是否在当前对象中,不会检查原型链。
原型
- for..in遍历对象时原理和[[Prototype]]类似,任何可以通过原型链访问到并且是可枚举的属性都会被枚举。使用in操作符检查属性在对象中是否存在时,同样会检查对象的整条原型链(无论属性是否可枚举)。
- 所有普通的[[Prototype]]链最终都会指向内置的Object.prototype对象。
- 当运行以下语句时:
如果foo存在与原型链上层时,会出现以下三种情况(第二第三中情况只会发生在使用=操作符来赋值时):myObject.foo="bar";
1.如果在[[Prototype]]链上层的foo没有被标记为只读,那就会直接在myObject上添加foo属性,它是屏蔽属性。
2.如果在[[Prototype]]链上层的foo被标记为只读,那么无法修改或者在myObject上创建屏蔽属性。
3.如果[[Prototype]]链上层的foo是一个setter,那么就会调用这个setter。foo不会被添加到myObject,也不会重新定义foo这个setter。 - JavaScript并不会复制对象属性。相反,JavaScript会在两个对象之间创建一个关联,这样一个对象就可以通过委托访问另一个对象的属性和函数,所以JavaScript中并没有继承,也没有类。
- 在JavaScript中对于“构造函数”最准确的解释是,所有带new的函数调用。
- 举例来说,Foo.prototype的.constructor属性只是Foo函数在声明时的默认属性,如果用一个新对象替换了函数默认的。prototype对象引用,那么新对象并不会自动获得.constructor属性。
- 如下面的代码
a1并没有.constructor属性,所以它会委托[[Prototype]]链上的Foo.prototype。但是这个对象也没有.constructor属性(不过默认的Foo.prototype对象有这个属性),所以它会继续委托,这次会委托给委托链顶端的Object.prototype。这个对象有.constructor属性,指向内置的Object()函数。function Foo(){} Foo.prototype={}; var a1=new Foo(); a1.constructor===Foo;//false a1.constructor===Object;//true
- 把一个对象关联到另一对象的安全的方式如下:
//ES6以前的方式 Bar.prototype=Object.create(Foo.protopyte) ; //ES6的方式 Object.setPrototypeOf(Bar.rototype,Foo.prototype);
- 在
a instanceof Foo;
中instanceOf
操作符的左操作数是一个普通对象,右操作数是一个函数。instanceOf
回答的问题是:在a
的整条[[Prototype]]链中是否有Foo.prototype
指向的对象?这个方法只能处理对象和函数之间的关系。 - .proto实际上并不存在于你正在使用的对象中,它和其他的常用函数(.toString(),.isPrototype(),等等)一样,存在与内置的Object.prototype中。此外,.proto看起来很像一个属性,但实际上它更像一个getter/setter
- [[Prototype]]机制就是指对象中的一个内部链接引用另一个对象。这个机制的本质就是对象之间的关联关系。
行为委托
- 委托行为意味着某些对象在找不到属性或者方法引用时会把这个请求委托给另一个对象。这种设计模式中的对象并不是按照父类到子类的关系垂直组织的,而是通过任意方向的委托关联并排组织的。
对象关联风格的代码:
Foo={
init: function(who){
this.me=who;
},
identify: function(){
return "I am " + this.me;
}
};
Bar = Object.create(Foo);
Bar.speak=function(){
alert("Hello, "+this.identify()+".");
};
var b1=Object.create(Bar);
b1.init("b1");
var b2=Object.create(Bar);
b2.init("b2");
b1.speak();
b2.speak();
相比面向对象风格来说,这段代码简洁了许多,我们是把对象关联起来,并不需要那些既复杂又令人困惑的模仿类的行为。