JavaScript 深入之史上最全 – 5种 this 绑定全面解析
this
绑定规则总共有下面 5 种:
- 默认绑定(严格/非严格模式)
- 隐式绑定
- 显式绑定
- new 绑定
- 箭头函数绑定
调用位置
调用位置就是函数在代码中 被调用的位置(不是声明的位置)。
查找方法:
- 分析调用栈:调用位置就是当前正在执行的函数的 前一个调用中
- 使用开发工具得到调用栈:设置断点或者插入
debugger;
语句,运行时调试器会在那个位置暂停,同时展示当前位置的函数调用列表,这就是 调用栈。找到栈中 第二个元素,这就是真正的调用位置。
绑定规则
默认绑定
独立函数调用
可以把默认绑定看作是无法应用其他规则时的默认规则,this 指向 全局对象;
严格模式
严格模式下,不能将全局对象用于默认绑定,this 会绑定到 undefined。只有函数运行在 非严格模式下,默认绑定才能绑定到全局对象。在严格模式下调用函数则不影响默认绑定。
隐式绑定
当函数引用有 上下文对象 时,隐式绑定规则会把函数中 this 绑定到这个上下文对象,对象属性引用链只有上一层或者说最后一层在调用中起作用。
隐式丢失
被隐式绑定的函数在特定情况下会丢失绑定对象,应用默认绑定,把 this 绑定到全局对象或者 undefined 上。
// 虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身。
// bar()是一个不带任何修饰的函数调用,应用默认绑定。
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函数别名
var a = "oops, global"; // a是全局对象的属性
bar(); // "oops, global"
参数传递就是一种隐式赋值,传入函数时也会被隐式赋值。回调函数丢失 this 绑定是非常常见的。
显式绑定
通过 call 或者 apply 方法进行绑定,第一个参数是一个对象,在调用函数时会将这个对象绑定到 this。
显式绑定无法解决绑定丢失问题。
new 绑定
在 JS 中,构造函数 只是使用 new 操作符时被调用的普通函数,它们不属于某个类,也不会实例化一个类。
包括内置对象函数在内的所有函数都可以使用 new 操作符来调用,这种函数调用被称为构造函数调用。
实际上并不存在所谓的构造函数,只有对函数的 构造调用。
使用 new 来调用函数,会自动执行下面的操作:
- 创建一个新对象
- 这个新对象会被执行 [[Prototype]] 连接
- 这个新对象会绑定到函数调用的 this
- 如果函数没有返回其他对象,那么 new 表达式中函数调用会自动返回这个新对象
优先级
new 绑定 -> 显式绑定 -> 隐式绑定 -> 默认绑定
绑定例外
被忽略的 this
把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定。
总是传入 null 来忽略 this 绑定可能会产生一些副作用,如果某个函数确实使用了 this,那默认绑定规则会把 this 绑定到全局对象中。
更安全的做法是传入一个特殊的对象(空对象),把 this 绑定到这个对象中不会对你的程序产生任何的副作用。
间接引用
间接引用下,调用这个函数会应用默认绑定规则。间接引用最容易在 赋值 时发生。
// p.foo = o.foo的返回值是目标函数的引用,所以调用位置是foo()而不是p.foo()或者o.foo()
function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4};
o.foo(); // 3
(p.foo = o.foo)(); // 2
软绑定
- 硬绑定可以把 this 强制绑定到指定对象(new 除外),防止函数调用应用默认绑定规则,但是会降低函数的灵活度,使用 硬绑定后就无法使用隐式绑定或者显式绑定来修改 this。
- 如果给默认绑定制定一个全局对象和 undefined 以外的值,那就实现和硬绑定相同的效果,同时保留隐式绑定或显式绑定修改 this 的能力。
// 默认绑定规则,优先级排最后
// 如果this绑定到全局对象或者undefined,那就把指定的默认对象obj绑定到this,否则不会修改this
if(!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
// 捕获所有curried参数
var curried = [].slice.call( arguments, 1 );
var bound = function() {
return fn.apply(
(!this || this === (window || global)) ?
obj : this,
curried.concat.apply( curried, arguments )
);
};
bound.prototype = Object.create( fn.prototype );
return bound;
};
}
this 词法
ES6 新增了一种特殊函数类型:箭头函数,箭头函数无法使用上述四种规则,而是根据外层(函数或全局)作用域(词法作用域)来决定 this。
箭头函数的绑定无法被修改。