第一章:关于 this
下面是一段关于 this 经典误解的代码
1 | function foo() { |
第二章:this 全面理解
绑定规则
默认绑定
规则
在非严格模式下,进行独立函数调用像下面这样,this
默认绑定到了 window
上。
1 | function foo() { |
在严格模式下,则不能将全局对象用于默认绑定,因此 this
会绑定到 undefined
,会报错
1 | function foo() { |
这里注意,在严格模式下调用 foo 不影响默认绑定
1 | function foo() { |
间接引用
间接引用最容易在赋值时发生:下面的代码中 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置时 foo() 而不是 p.foo() 或者 o.foo()。这里会应用默认绑定。
1 | function foo() { |
隐式绑定
规则
当函数引用有上下文对象时, 隐式绑定规则会把函数调用中的 this
绑定到这个上下文对象。
1 | function foo() { |
对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。
1 | function foo() { |
隐式丢失
隐式丢失的问题:丢失绑定对象,应用默认绑定
- 函数别名
下面这段代码中虽然 bar
是 obj.foo
的一个引用,但实际上,它引用的是 foo
函数本身,因此 bar()
其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
1 | function foo() { |
- 函数传参
1 | function foo() { |
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值
传入语言内置函数,与上面的结果相同
1 | function foo() { |
显式绑定
call、apply、bind
1 | function foo() { |
绑定之后就不能再修改 this
的指向。
包裹函数
典型的一个应用场景就是创建一个包裹函数,负责接收参数并返回值
1 | function foo(something) { |
辅助函数
1 | function foo(something) { |
bind
1 | function foo(something) { |
new绑定
使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行 [[Prototype]] 连接。
- 这个新对象会绑定到函数调用的 this。
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
1 | function foo(a) { |
优先级
可以按照下面的顺序进行判断:
- 函数是否在 new 中调用(new绑定)?如果是的话this绑定的是新创建的对象。
var bar = new foo()
- 函数是否通过call、apply(显式绑定)或者bind(硬绑定)调用。
var bar = foo.call(obj2)
- 函数是否在某个上下文对象中调用(隐式绑定)。
var bar = obj1.foo()
- 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到
undefined
,否则绑定到全局对象。var bar = foo()
绑定例外
如果把 null 或者 undefined 作为 this 的绑定对象传入 call、apply或者bind,这些值在调用时会被忽略,实际上应用的是默认绑定规则。
1 | function foo() { |
柯里化
一种非常常见的做法是使用apply()来展开一个数组,并当作参数传入一个函数。类似的,bind() 可以对参数进行柯里化(预先设置一些参数)
1 | function foo(a, b) { |
这两种方法都需要传入一个参数当做 this 的绑定对象
Object.create(null)
Object.create(null) 和 {} 很像,但是不会创建 Object.prototype
这个委托,所有它比 {} 更空,这样设置比设置为 null 和 undefined 更安全。
1 | function foo(a, b) { |
第三章: 对象
属性描述符
1 | var myObject = { |
创建普通属性时属性描述符会使用默认值,我们也可以使用 Object.defineProperty() 来添加一个新属性或者修改一个已有属性(如果它是 configurable)并对特性进行设置。一般来说不会使用。
1 | var myObject = {} |
writable
决定是否可以修改属性的值。
1 | var myObject = {} |
在上面的非严格模式下,只是修改静默失败,如果在严格模式下,这种方法会报错。
configurable
1 | var myObject = {} |
可以看到把 configurable 修改为 false 是单向操作,无法撤销!
有一个小小的例外:即便属性是 configurable: false
,我们还是可以把 writable
的状态由 true
改为 false
,但是无法由 false
改为 true
。
除了无法修改, configurable: false
还会禁止删除这个属性。
1 | var myObject = { |
enumerable
这个是控制属性是否会出现在对象的属性枚举中,比如 for in 循环,如果把 enumerable 设置为 false,这个属性就不会出现在枚举中,虽然仍然可以正常访问它,设置为 true 就会让它出现在枚举中。
下面的代码可以检验是否为可枚举:
propertyIsEnumerable()
会检查给定的属性名是否直接存在于对象中(而不是在原型链上)并且满足 enumerable:true
。
Object.keys()
会返回一个数组,包含所有可枚举属性,Object.getOwnPropertyNames()
会返回一个数组,包含所有属性,无论它们是否可枚举。这两个方法都只会查找对象直接包含的属性。
1 | var myObject = {} |
存在性
1 | var myObject = { |
可以应用 in 和 hasOwnProperty 来进行判断属性是否真的存在于对象中。
in hasOwnProperty
1 | var myObject = { |
in 操作符会检查属性是否在对象及其 [[Prototype]]原型链 中,hasOwnProperty() 只会检查属性是否在 myObject对象中,不会检查 [[Prototype]]链。