JavaScript你必须了解的知识 --- this

263 阅读5分钟

先说结论:this是自动定义在函数作用域上,按照new绑定,call/appl/bind绑定,对象调用,默认绑定window的顺序依次定义this指向

this是什么

想象下这样的一个场景,你和同事再吐槽某个人的时候,很多情况下并不会点名道姓,会用一个外号来代替。假如又有一天,你换了吐槽对象,你还可以用这个代号来代替你要吐槽的人

​ 在js中,this就是你要调用的一个对象,他自动定义在所有函数的作用域中,根据你调用它的场合,this指代的具体对象也不一样

怎么用好this(this的绑定规则)

想要用好this,你就必须掌握其绑定规则

【扩展】调用位置(隐式调用前置知识)

调用位置就是函数在代码中被调用的位置(当前正在执行的函数的前一个调用中),分析调用位置我们需要确认调用栈

下面用一个简单的demo体现

function fun3() {
    console.log("fun3")
}

function fun2() {
    console.log("fun2")
    fun3();//fun3的调用位置
}

function fun1() {
    console.log("fun1")
    fun2();//fun2的调用位置
}
debugger;
fun1();//fun1的调用位置

默认绑定

前文已经说过this是自动定义在所有函数的作用域中,其this执行的就是默认绑定

在正常模式和严格模式下,其this指向不同

  • 正常模式下this指向window
  • 严格模式下this是undefined
var a=1;

function fun1() {
    console.log("正常模式下的this指向", this.a)// 1
}

function fun2() {
    "use strict"
    console.log("严格模式下的this指向", this.a)// test.html:36 Uncaught TypeError: Cannot read property 'a' of undefined
}

隐式绑定

本案例中分析了对象属性引用链 / 对象属性引用链的值作为函数引用 / 对象属性引用链的值作为回调来举例说明this指向

function fun() {
    console.log(this.a);
}

var obj2 = {
    a: '这是obj2中的a',
    fun: fun
};
var obj1 = {
    a: '这是obj1中的a',
    obj2: obj2
};
var a = "这是window下的a";

debugger
obj2.fun();//??? 

obj1.obj2.fun();//???

var outFun = obj2.fun
outFun();//???

function cb(fn) {
    fn();
}
cb(obj2.fun)//???
obj2.fun()obj1.obj2.fun()outFun()cb(obj2.fun)
这是obj2中的a这是obj2中的a这是window下的a这是window下的a

总结:

  • 函数调用的过程中,如果是通过对象属性引用链执行函数,this绑定的是对象属性引用链中最顶层或者说最后一个obj

  • 而通过将对象属性引用链的值作为函数引用或者回调函数的参数执行,this为默认绑定

关于这两点可以通过调用位置来进行理解

  • 当通过对象属性引用链执行函数时,上一个调用是对象,其函数调用位置是在对象内部,此时this指向的是对象
  • 当对象属性引用链作为函数引用被保存时,无论时作为函数执行还是作为回调参数,其调用位置都是函数,而函数this默认绑定的就是window

练习1:

function fun() {
    console.log(this.a);
}
var obj2 = {
    a: '这是obj2中的a',
    fun: fun
};
var obj3 = {
    a: '这是obj3中的a',
    fun: function () {
        console.log(this.a);
        obj2.fun()
    }
}
obj3.fun()//???  ???
var outFun3 = obj3.fun;
outFun3()//???  ???

答案见文章底部

显示绑定

通过call,apply,bind来强制规定this的指向称之为显示绑定

  • call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

    function.call(thisArg, arg1, arg2, ...)

  • apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

    func.apply(thisArg, [argsArray])

  • bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用

    function.bind(thisArg[, arg1[, arg2[, ...]]])

function foo() {
	console.log( this.a );
}
var obj = {
	a:2
};
foo.call( obj ); // 2
  • 硬绑定

    针对上文中的demo,就是要得到obj2中的a,该怎么做

function fun() {
    console.log(this.a);
}

var obj2 = {
    a: '这是obj2中的a',
    fun: fun
};
var a = "这是window下的a";
var outFun = obj2.fun
outFun(); //???

方法一

请思考下最后一行的结果

function fun() {
    console.log(this.a);
}

var obj2 = {
    a: '这是obj2中的a',
    fun: fun
};
var a = "这是window下的a";


function fun1() {
    obj2.fun.apply(obj2)
}
var outFun = fun1;
outFun(); 

outFun.apply(window);//思考下结果

方法二

请思考下最后一行的结果

function fun() {
    console.log(this.a);
}

var obj2 = {
    a: '这是obj2中的a',
    fun: fun
};
var a = "这是window下的a";

var outFun = obj2.fun.bind(obj2)
outFun(); 
outFun.apply(window);//思考下结果

很明显两次结果都是obj2中的a,所以说通过bind绑定后得到的函数不能够通过apply和call修改this的指向,这种方式称之为硬绑定,同时是不是也发现bind背后的原理了

new绑定

对于该规则下this指向,我们需要先了解下,通过new 来调用函数时,构造函数内部都发生了什么

先看demo

function Person(name,age){
    this.name = name;
    this.age = age;
}
const person = new Person('zhangsan',10);
person.name//zhangsan
person.age//10

通过new调用构造函数之后,新创建的person实例也能够访问到构造函数this里面的内容,要达到如上要求,构造函数内部必须要做如下事情

  • 创建(或者说构造)一个全新的对象。
  • 这个新对象会被执行[[ 原型]] (_proto_)连接。
  • 这个新对象会绑定到函数调用的this。
  • 如果函数没有返回其他对象,那么new 表达式中的函数调用会自动返回这个新对象。

通过上述说明,new绑定可以总结为:

new会构造一个新对象,并把创建的新对象,绑定到构造函数调用中的this上。

优先级

new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

特殊的绑定规则

  • null 或者undefined 作为this 的绑定对象传入call、apply 或者bind,应用的是默认绑定,值为window(正常模式)

  • 赋值表达式中的this

    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(),根据我们之前说过的,这里会应用默认绑定。

  • 箭头函数

    箭头函数不使用this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。

    function fun() {
        // 返回一个箭头函数
        return (a) => {
            //this 继承自fun()
            console.log(this.a);
        };
    }
    var obj1 = {
        a: 2
    };
    var obj2 = {
        a: 3
    }
    var bar = fun.call(obj1);
    bar.call(obj2);//结果是2,后面的call没有用了
    

    原稿都放在-->点击,有需要自取

练习1结果:这是obj3中的a / 这是obj2中的a || undefined / 这是obj2中的a