毫无疑问,默认绑定的优先级是四条规则中最低的。显式绑定优先于隐式绑定。
隐式和 new 绑定的优先级
function foo(n) {
this.age = n;
}
var age = 999;
let obj = {
age: 3,
foo
}
let a = new obj.foo(6666);
console.log(obj.age); // 3
console.log(a.age); // 6666
可以看到 new 绑定比隐式绑定优先级高。
硬绑定和 new 绑定的优先级
Function.prototype.bind(..)
会创建一个新的包裹函数,这个函数会忽略它当前的 this
绑定(无论绑定的对象是什么),并把我们提供的对象绑定到 this 上。
看起来硬绑定优先级高于 new 绑定。
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
barr( 2 ); // 修改的是 obj 的 a
console.log( obj1.a ); // 2
var bazz = new bar(3);
console.log( obj1.a ); // obj 的 a 还是 2!!
console.log( bazz.a ); // 3
虽然 bar 被硬绑定到了 obj1 对象上,但是对 bar 进行 new bar(3)
并没有像我们预计的那样把 obj 的 a 修改为 3。就好像绑定的 this 和 new 创建的 this 不是一个 this。
为什么?
其实,是由于 bind 的 JavaScript 的内部实现,会判断 bind 后的函数是否被 new 调用,如果是的话就会创建新的 this 覆盖硬绑定的 this。
绑定顺序
- 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象
- 否则,函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象
- 否则,函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。
- 否则,如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。
不过…… 凡事总有例外。
例外
this 被故意忽略
你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值
在调用时会被忽略,实际应用的是 ** 默认绑定规则 **
function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2
在 ES5 中,一种非常常见的做法是使用 apply(..)
来 “展开” 一个数组,并当作参数传入一个函数。类似地,bind(..)
可以对参数进行柯里化(预先设置一些参数),这种方法有时非常有用。
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 把数组 “展开” 成参数
foo.apply( null, [2, 3] ); // a:2, b:3
// 使用 bind(..) 进行柯里化
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3
如果函数并不关心 this 的话,你仍然需要传入一个占位值,这时 null 可能是一个不错的选择,就像代码所示的那样。
只有当你确定某个函数的 this 可以执行默认绑定时,再用 bull 绑定忽略 this。
或者,你可以使用一个真正的空对象来代替 null,作为函数的 this,避免在执行默认绑定时,影响全局的 this。
这个真正的空对象就是通过 Object.create(null)
创建的,虽然和 {}
很像,但是这个对象没有 prototype。
let kull = Object.create(null);
kull.prototype // undefiend
let obj = {
a :2
}
kull.__proto__ = obj;
kull.a // undfined
kill.a = 3
kull.a // 3
可以看到,这个空对象无法进行继承
间接引用
你有可能(有意或者无意地)创建一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则。
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
赋值表达式 p.foo = o.foo 的返回值是目标函数的引用,因此调用位置是 foo() 而不是
p.foo() 或者 o.foo()。相当于直接调用了 foo 函数,因此执行默认绑定
软绑定
硬绑定会大大降低函数的灵活性,使用硬绑定之后就无法使用隐式绑定或者显式绑定来修改 this。
如果可以给默认绑定指定一个 ** 非全局对象和 undefined 值 **,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改 this 的能力。
if (!Function.prototype.softBind) {
Function.prototype.softBind = functtion(obj) {
let fn = this;
// 得到第二个 - 最后一个参数
// 注意区分这里的 arguments 和下面的 arguments 不是一个!
// 这里的 arguments 是调用 softBind 传入的参数
let curried = Array.prototype.slice.call(arguments, 1);
let bound = function () {
return fn.apply(
// this 不存在或者 this 是全局对象,则修改 this 为你指定的 obj,否则不修改!
(!this || this === (window || global)) ?
obj :
this,
// 这里的 arguments 是调用 bound 函数传入的参数
curried.concat.apply(curried, arguments)
);
};
bound.prototype = Object.create(fn.prototype);
return bound;
}
}
除了软绑定之外,softBind(..)
的其他原理和 ES5 内置的 bind(..)
类似。它会对指定的函数进行封装,首先检查调用时的 this,如果 this 绑定到了全局对象或者 undefined
,那就把指定的默认对象 obj 绑定到 this,否则不会修改 this。此外,这段代码还支持可选的柯里化。
下面我们看看 softBind 是否实现了软绑定功能:
var a = "全局的 a 变量"
function foo() {
console.log(this.a);
}
let obj1 = {
a: '对象 1 的 a 变量'
}
let obj2 = {
a: '对象 2 的 a 变量'
}
let f = foo.softBind(obj1);
f() // 对象 1 的 a 变量
obj2.foo2 = foo.softBind(obj1);
obj2.foo2() // 对象 2 的 a 变量
可以看到,软绑定时 this 可以手动改绑到 obj2 上,如果代码想应用默认绑定,则会绑定到 obj1 上,避免绑定到全局对象上。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。