2.3 this 绑定规则的优先级

上篇《2.2 this 全面解析》了解到函数调用中 this 绑定的四条规则,找到函数的调用位置并判断应当应用哪条规则。这篇学习假设某个调用位置可以应用多条规则,就必须给这些规则设定优先级。

毫无疑问,默认绑定的优先级是四条规则中最低的,所以可以先不考虑它。

那么隐式绑定和显式绑定哪个优先级更高?

function foo() {
    console.log(this.a)
}
var obj1 = {
    a: 2,
    foo: foo
}
var obj2 = {
    a: 3,
    foo: foo
}


obj1.foo()  // 2
obj2.foo()  // 3

obj1.foo.call(obj2) // 3
obj2.foo.call(obj1) // 2

结果:显式绑定优先级更高,也就是说在判断时应当先考虑是否可以应用显式绑定。

现在需要搞清楚 new 绑定和隐式绑定的优先级谁高谁低:

function foo(something) {
    this.a = something
}

var obj1 = {
    foo: foo
}

var obj2 = {}

obj1.foo(2)
console.log(obj1.a)  // 2

obj1.foo.call(obj2, 3)  
console.log(obj2.a) // 3

var bar = new obj1.foo(4)
console.log(obj1.a) //2
console.log(bar.a)  //4

结论:new 绑定比隐式绑定优先级高

new 绑定和显式绑定谁的优先级更高呢?

拓展: newcall/apply 无法一起使用,因此无法通过 new foo.call(obj1) 来直接测试。但是可以使用硬绑定来测试它们的优先级。

function foo(something) {
    this.a = something
}

var obj1 = {}

var bar = foo.bind( obj1 )
bar(2)
console.log(obj1.a) 	//2

var baz = new bar(3)
console.log(obj1.a) 	//4
console.log(baz.a)  	//4

解析:

  1. bar 被硬绑定到 obj1 上,但是 new bar(3) 并没有把 obj1.a 修改为 3
  2. new 修改了硬绑定(到 obj1 的)调用 bar(...) 中的 this
  3. 因为使用了 new 绑定,得到了一个名字为 baz 的新对象,并且 baz.a 的值是 3

再回头看看之前介绍的 “裸” 辅助函数 bind :

function bind(fn, obj) {
    return function () {
        fn.apply(obj, arguments)
    }
}

因为看起来在辅助函数中 new 操作符的调用无法修改 this 绑定,但是在刚才的代码中 new 确实修改了 this 绑定。
实际上, ES5 中内置的 Function.prototype.bind(...) 更加复杂。
下面是引用 MDN 提供的一种 bind(...) 实现:

if (!Function.prototype.bind) {
    Function.prototype.bind == function (oThis) {

        if (typeof this !== "function") {
            // 与 ECMAScript 5 最接近的内部 IsCallable 函数
            throw new TypeError(
                "Function.prototype.bind - what is trying " + "to be bound is not callable"
            )
        }
        var aArgs = Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function () { },
            fBound = function () {
                return fToBind.apply((
                    this instanceof fNOP && oThis ? this : oThis
                ), aArgs.concat(
                    Array.prototype.slice.call(arguments)
                ))
            }
        fNOP.prototype = this.prototype
        fBound.prototype = new fNOP()
        return fBound
    }
}

拓展:

这种 bind(..) 是一种 polyfill 代码 (polyfill 就是我们常说的刮墙用的腻子, polyfill 代码主要用于旧浏览器的兼容,比如说在旧的浏览器中并没有内置 bind 函数,因此可以使用 polyfill 代码在旧浏览器中实现新的功能),对于 new 使用的硬绑定函数来说,这段 polyfill 代码和 ES5 内置的 bind(..) 函数并不完全相同。由于 polyfill 并不是内置函数,所以无法创建一个不包含 .prototype 的函数。因此会具有一些副作用。如果你要在 new 中使用硬绑定函数并且依赖 polyfill 代码的话,一定要非常小心。

下面是 new 修改 this 的相关代码:

 this instanceof fNOP && oThis ? this : oThis
 // 	... 以及
 fNOP.prototype = this.prototype
 fBound.prototype = new fNOP()

简单来说,这段代码会判断硬绑定函数是否被 new 调用,如果是的话就会使用新创建的 this 替换硬绑定的 this

那为什么要在 new 中使用硬绑定函数呢? 直接使用普通函数不是更简单吗?
之所以要在 new 中使用硬绑定函数,主要目的是预先设置函数的一些参数,这样在使用 new 进行初始化时就可以只传入其余的参数。bind(...) 的功能之一就是可以把除了第一个参数(第一个参数用于绑定 this)之外的其他参数都传给下层的参数(这种技术称为“部分应用”,是“柯里化”的一种)。

function foo(p1, p2) {
    this.val = p1 + p2
}
// 之所以使用 null 是因为在本例中并不关心硬绑定的 this 是什么
// 反正使用 new 时 this 会被修改
var bar = foo.bind(null, "p1")
var baz = new bar("p2")

console.log(baz.val)
---------------------------------------------
> p1p2

总结

可以按照下面的顺序来根据优先级来判断函数在某个调用位置应用的是哪条规则:

  1. 函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象(var bar = new foo()
  2. 函数是否通过 callapply (显式绑定) 或者硬绑定调用?如果是的话,this 绑定的是指定的对象(var bar = foo.call(obj2)
  3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是哪个上下文对象(var bar = foo()
  4. 如果都不是的话,使用默认绑定。如果再严格模式下,就绑定到 undefined, 否则绑定到全局对象(var bar = foo()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值