美文网首页JavaScriptWeb前端之路程序员
你不知道的JavaScript之this篇(三)

你不知道的JavaScript之this篇(三)

作者: 2f1b6dfcc208 | 来源:发表于2017-09-06 18:10 被阅读20次

上一篇文章已经详细介绍了函数调用中this绑定的四条规则,我们所需要做的就是找到函数的调用位置并判断应用哪条规则,不过,如果某个调用位置可以应用多条规则该怎么办?为了解决这个问题就必须给这些规则设定优先级,对于正常的函数调用来说,优先级为 new绑定 > 显示绑定 > 隐式绑定 > 默认绑定,所以我们可以按照下面的顺序来进行判断:

  1. 函数是否在new中调用(new 绑定) ? 如果是的话this绑定的是新创建的对象。
    var bar = new foo() //此处foo函数里面的this指向bar
    注: 可以在new中使用硬绑定函数,如下
function foo(p1,p2){
    this.val = p1 + p2;
}
var bar = foo.bind(null,'p1')
var baz = new bar('p2')
console.log(baz.val);//p1p2

此处的this指向新创建的对象,之所以要在new中使用硬绑定函数,主要目的是预先设置函数的一些参数,这样在使用new进行初始化时就可以只传入其余的参数。bind(...)的功能之一就是可以把除了第一个参数(第一个参数用于绑定this)之外的其他参数都传给下层的函数(这种技术称为“部分应用”,是“柯里化”的一种)

  1. 函数是否通过call、apply(显示绑定)或者bind(硬绑定)调用?如果是的话,this绑定的是指定的对象
    var bar = foo.call(obj2) //此处foo函数里的this指向obj2
  2. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象(注意有隐式丢失的情况)
    var bar=obj1.foo() //此处this指向obj1
  3. 如果都不是的话,使用默认绑定,严格模式下this绑定到undefined,非严格模式下绑定到全局对象
    var bar = foo() //此处foo函数里面的this指向全局对象

但是在某些场景下this的绑定行为会出乎意料,你认为应当应用其他绑定规则时,实际上应用的可能是默认绑定规则,这被称为绑定例外。

被忽略的this

  • 当把null或者undefined作为this的绑定对象传入call、apply或者bind时,,这些值在调用的时候会被忽略,实际应用的是默认绑定规则:
function foo(){
    console.log(this.a);
}
var a = 2;
foo.call(null); //2   所以此处foo函数里的this指向全局对象

一般在什么样的情况下我们会传入null呢,比如某个函数接收多个参数,但此时我们想传入的却是一个数组,便可以使用apply(...)来接收这个参数数组,类似地,使用bind(...)可以对参数进行柯里化(预先设置一些参数)

function foo(a,b){
    console.log("a:"+a+",b:"+b);
}
//通过使用apply传入数组
foo.apply(null,[2,3]);//a:2,b:3

//使用bind进行柯里化
var bar = foo.bind(null,1);
bar(2);//a:1,b:2

这两种方法都需要传入一个参数当作this的绑定对象。如果函数内部根本不关心this的话,我们仍需要传入一个占位符,这时null便成了一个不错的选择,在ES6中,可以用...扩展运算符代替上面的apply方法展开数组,这样可以避免不必要的this绑定,但仍然没有柯里化的相关语法,因此还是需要使用bind(...)
  然而,总是使用null来忽略this绑定可能产生一些副作用,如果某个函数内部确实使用了this(比如第三方库中的一个函数),那默认绑定规则会把this绑定到全局对象(在浏览器中这个对象是window),这将导致无法预料的后果(比如修改了全局对象的属性),显而易见,这种方式可能会导致许多难以分析和追踪的bug.
  一种“更安全”的做法是传入一个特殊的对象,把this绑定到这个对象不会对我们的程序产生任何副作用。就像网络(以及军队)一样,我们可以创建一个“DMZ“(demilitarized zone,非军事区)对象--它就是一个空的非委托对象(何为非委托对象?后续继续了解).如果我们在忽略this绑定时总是传入一个DMZ对象,那就什么都不用担心了,因为任何对于this的使用都会被限制在这个空对象中,不会对全局对象产生任何影响。一般可使用∅变量名来表示这个空对象。在JavaScript中创建一个对象最简单的方法是Object.create(null)。它和{}很像,但是并不会创建Object.prototype这个委托,所以它比{}“更空”:

function foo(a,b){
    console.log("a:"+a+",b:"+b);
}
const ∅=Object.create(null);
foo.apply(∅,[2,3]);  //a:2,b:3
const bar=foo.bind(∅,2);
bar(3);  //a:2,b:3

通过使用∅这个空对象不仅让函数变得更加“安全”,而且可以提高代码的可读性。

  • 另一种this被忽略的情况发生在隐式绑定被间接引用时,此时,会应用默认绑定规则。
    常见的间接引用有两种情况:
    赋值:将某个隐式绑定的函数句柄赋值给一个变量,然后通过这个变量执行函数,这便属于间接引用,函数里的this将应用默认绑定规则,不再指向隐式绑定的那个对象
    当作参数传入函数时,一般作用回调函数使用,其实传参相当于一种隐式赋值,和上面一样,应用默认绑定规则。
    注意:对于默认绑定来说,决定this绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式,this会被绑定到undefined,否则this会被绑定到全局对象。
软绑定

之前我们已经看到过,硬绑定这种方式可以把this强制绑定到指定的对象(除了使用new时),防止函数调用应用默认绑定规则。问题在于,硬绑定会大大降低函数的灵活性,使用硬绑定之后就无法使用隐式绑定或者显式绑定来修改this。
如果可以给默认绑定指定一个全局对象和undefined以外的值,那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改this的能力。
可以通过一种被称为软绑定的方法来实现我们想要的效果:

if(!Function.prototype.softBind){
    Function.prototype.softBind=function(obj){
        const fn = this;//这里的this取决于softBind函数的调用位置
        const carried = Array.prototype.slice.call(arguments,1);
        const bind = function(){
            return fn.apply(
                (!this||this===window)?obj:this,//这里的this由软绑定后的函数调用位置决定,注意,与上面的this不同
                Array.prototype.concat.apply(carried,arguments)
           );
        }
        bind.prototype = Object.create(fn.prototype);
        return bind;
    }
}

除了软绑定之外,softBind(...)的其他原理和ES5内置的bind(...)类似,它会对指定的函数进行封装,首先检查调用时的this,如果this绑定到全局对象或者undefined,那就把指定的默认对象obj绑定到this,否则不修改this。此外,这段代码还支持可选的柯里化。

function foo(){
    console.log("name:"+this.name);
}
const obj1={name:"obj1"},obj2={name:"obj2"},obj3={name:"obj3"};
const fooObj=foo.softBind(obj1);
fooObj();//name:"obj1"  软绑定
obj2.foo=foo.softBind(obj1);
obj2.foo();//name:"obj2"  隐式绑定修改了this
fooObj.call(obj3);//name:"obj3"  显示绑定修改了this

setTimeout(obj2.foo,100);//name:"obj1"  当应用默认绑定时,则会应用我们自定义的软绑定(在node环境下显示undefined,不知此处为何依然应用了默认绑定)

注意:箭头函数

在ES6中定义了一种新的函数类型:箭头函数,箭头函数并不是使用function关键字定义的,而是使用被称为“胖箭头”的操作符=>定义的,箭头函数本身取消了this机制,使用更常见的词法作用域替代this,箭头函数里的this继承自外层函数的this。箭头函数的绑定无法被修改,new也不行。

相关文章

网友评论

    本文标题:你不知道的JavaScript之this篇(三)

    本文链接:https://www.haomeiwen.com/subject/uemmjxtx.html