前端面试必杀技:this是个啥?

讲下函数调用

  • 函数调用三种方法(ES5):
    • func(p1, p2)
    • obj.child.method(p1, p2)
    • func.call(context, p1, p2) // 先不讲 apply
  • 第三中调用形式才是正确的调用形式,其他两种都是语法糖,可以等价的变换:
// 称此代码为「转换代码」
func(p1, p2) 等价于
func.call(undefined, p1, p2)
 
obj.child.method(p1, p2) 等价于
obj.child.method.call(obj.child, p1, p2)
// 第二个等价举例
var obj = {
  foo: function(){
    console.log(this)
  }
}
obj.foo()
// 等价于
obj.foo.call(obj)
复制代码
  • 所以,this 就是你 call 一个函数时,传入的 context。
  • 如果你的函数调用形式不是 call 形式,请按照「转换代码」将其转换为 call 形式。
  • 但是因为本文章是后来更新的缘故,只用「转换代码」来讲解其中的[]调用,其他的调用还是按照文章初创时的思路;

独立调用

  • 默认绑定规则:this绑定给window;
  • 在严格模式下,默认绑定规则会把this绑定undefined上;
function foo() {
	console.log( this.a );
}
var a = 2;
(function(){
	"use strict";
	foo(); //2
})();
复制代码
  • 这里有一个微妙但是非常重要的细节,虽然 this 的绑定规则完全取决于调用位置,但是只有 foo()运行在非 严格模式下时,默认绑定才能绑定到全局对象; 严格模式下调用foo()不会影响默认绑定规则;
function foo() {
	"use strict";
	console.log( this.a );
}
var a = 2;
foo(); //undefined
复制代码
  • 无论函数是在哪个作用域中被调用,只要是独立调用则就会按默认绑定规则被绑定到全局对象或者undefined上。

隐式调用:(使用对象的属性调用)

隐式绑定

  • 隐式绑定的规则:

    • this给离函数最近的那个对象;
    • 判断函数调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含;
    //隐式绑定的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含
    //当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象
    function foo() {
    	console.log( this.a );//2
    }
    var obj = {
    	a: 2,
    	foo: foo
    };
    obj.foo(); 
    复制代码
    • 当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象;
    • 对象属性引用链中只有最顶层或者说最后一层会影响调用位置;
    //对象属性引用链中只有最顶层或者说最后一层会影响调用位置
    function foo() {
    	console.log( this.a );
    }
    var obj2 = {
    	a: 42,
    	foo: foo
    };
    var obj1 = {
    	a: 2,
    	obj2: obj2
    };
    obj1.obj2.foo(); //42
    复制代码

隐式丢失

  • 将函数通过隐式调用的形式赋值给一个变量;
注意:经典面试题,这是一个隐式丢失:
function foo() {
	console.log( this.a );//oops, global
}
var a = "oops, global"; 
var obj = {

	a: 2,
	foo: foo
};
var bar = obj.foo; //把obj.foo赋予别名bar,造成了隐式丢失,因为只是把foo()函数赋给了bar,而bar与obj对象则毫无关系
bar(); 

//等价于
var a = "oops, global"; 
var bar = function foo(){
    console.log( this.a );
}
bar();//oops, global
复制代码
  • 将函数通过隐式调用的形式进行传参;
var a = 0;
function foo(){
    console.log(this.a);
};
function bar(fn){
    fn();
}
var obj = {
    a : 2,
    foo:foo
}
//把obj.foo当作参数传递给bar函数时,有隐式的函数赋值fn=obj.foo。与上例类似,只是把foo函数赋给了fn,而fn与obj对象则毫无关系。
bar(obj.foo);//0

//等价于
var a = 0;
function bar(fn){
    fn();
}
bar(function foo(){
    console.log(this.a);
});
复制代码
  • 内置函数:内置函数与上例类似,也会造成隐式丢失
var a = 0;
function foo(){
    console.log(this.a);
};
var obj = {
    a : 2,
    foo:foo
}
setTimeout(obj.foo,100);//0

//等价于
var a = 0;
setTimeout(function foo(){
    console.log(this.a);
},100);//0
复制代码

显式绑定:call()、apply()、bind()

  • 通过call()、apply()、bind()方法把对象绑定到this上,叫做显式绑定。
//普通对象的属性查找 
function foo(a,b) {
	console.log( this.a,a,b );
}
var obj = {
	a:2
};
foo.call( obj,"a","b"); //2 a b
foo.apply(obj,["a","b"])//2 a b
复制代码
  • 显式绑定规则:call,apply和bind指定的对象(第一个参数);
  • 硬绑定:硬绑定是显式绑定的一个变种,使this不能再被修改。它有一个包裹函数,有一个目标函数的显示调用(bind,返回只是一个函数);可以用来解决隐式丢失。
//	我们来看看这个显式绑定变种到底是怎样工作的。我们创建了函数 bar() ,并在它的内部手动调用了 foo.call(obj) ,因此强制把 foo 的 this 绑定到了 obj 。无论之后如何调用函数 bar ,它总会手动在 obj 上调用 foo 。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。
function foo() {
	console.log( this.a );
}
var a =1;
var obj = {a:2};
var obj_test = {a:"test"};
var bar = function() {
	console.log( this.a );
	foo.call( obj );};
bar(); // 1 2
setTimeout( bar, 1000 ); // 1 2
bar.call( obj_test ); //test  2   
//硬绑定的bar不可能再修改它的this(指的是foo中的this)

//硬绑定的典型应用场景就是创建一个包裹函数,传入所有的参数并返回接收到的所有值
	function foo(arg1,arg2) {
		console.log( this.a,arg1,arg2);
		return this.a + arg1;
	}
	var obj = {a:2};
	var bar = function() {
		return foo.apply( obj, arguments);
	};
	var b = bar(3,2); // 2 3 2
	console.log( b ); // 5
复制代码

new绑定

//3. 这个新对象会绑定到函数调用的 this 。
function foo(a) {
	this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2		
//使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。 new 是最
//后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定。	
复制代码

绑定例外

箭头函数

  • 箭头函数:this的绑定和作用域有关。如果在当前的箭头函数作用域中找不到变量,就向上一级作用域里去找。
  • 箭头函数内部的 this 是词法作用域,由上下文确定,此作用域称作 Lexical this ,在代码运行前就可以确定。没有其他大佬可以覆盖。
  • 这样的好处就是方便让回调函数的this使用当前的作用域,不怕引起混淆。所以对于箭头函数,只要看它在哪里创建的就行。
function foo() {
	 setTimeout(() => {
	    console.log('id:', this.id); //id: 42
	  }, 100);
}
var id = 21;
foo.call({ id: 42 })

// 再举个栗子
var returnThis = () => this
returnThis() // window
new returnThis() // TypeError
var boss1 = {
  name: 'boss1',
  returnThis () {
    var func = () => this
    return func()
  }
}
returnThis.call(boss1) // still window
var boss1returnThis = returnThis.bind(boss1)
boss1returnThis() // still window
boss1.returnThis() // boss1
var boss2 = {
  name: 'boss2',
  returnThis: boss1.returnThis
}
boss2.returnThis() // boss2
复制代码

被忽略的this:null\undefined

  • 当被绑定的是null 或者 undefined,则this是默认的 context(严格模式下默认 context 是 undefined);
//	如果你把 null 或者 undefined 作为 this 的绑定对象传入 call 、 apply 或者 bind ,这些值在调用时会被忽略,实际应用的是默认绑定规则;
function foo() {
	console.log( this.a );
}
var a = 2222;
foo.call( null ); // 2
复制代码

柯里化

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

//升级版:不会污染全局	
function foo(a,b) {
	this
	console.log( "a:" + a + ", b:" + b );
}
// 我们的DMZ空对象,“DMZ”(demilitarized zone,非军事区)
var ø = Object.create( null );//{}
// 把数组展开成参数
foo.apply( ø, [2, 3] ); // a:2, b:3
// 使用bind(..)进行柯里化
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3
复制代码

[]调用

  • 很特殊的一个调用,有点出乎意料的结果,特此记录;
function fn (){ console.log(this) }
var arr = [fn, fn2]
arr[0]() // 这里面的 this 又是什么呢?
复制代码
  • 这个可以用文章最前面讲的函数调用方法来想:
// 等价于
arr[0]()
假想为    arr.0()
然后转换为 arr.0.call(arr)
那么里面的 this 就是 arr 了
复制代码

总结

  • this是函数执行的上下文对象;
  • 根据函数调用的方式不同this的值也不同:
    • 1.以函数的形式直接调用,this是window
    • 2.以对象方法的形式调用,this是调用方法的对象
    • 3.以构造函数的形式调用,this是新创建的那个对象
    • 4.使用call、apply和bind调用的函数,第一个参数就是this
    • 5.在构造函数 prototype 属性中被调用,this仍然指的是实例对象;
    • 6.在事件处理函数中:this是指触发当前事件的HTML DOM节点元素;
    • 7.箭头函数中,this指的是创建时的词法作用域;
  • 注意:
    • 在函数中 this 到底取何值,是在函数真正被调用执行的时候确定下来的,函数定义的时候确定不了。
    • this本身不具备任何含义。
<script>
    // TODO 一、以全局&调用普通的函数的形式调用,this是window.
    function fn(){
        console.log(this);
    }
    fn();


    //二、构造函数
    //如果函数作为构造函数使用,那么其中的this就代表即将new出来的对象
    function Objfn(){
        this.a = 10;
        console.log(this);//此时输出的是对象 Objfn {a: 10}
    }
    var objfn = new Objfn();
    console.log('objfn.a='+objfn.a);//objfn.a=10
    
    //但是如果直接调用Objfn1()函数,而不是new Objfn1(),那么情况就变成了Objfn()是普通函数
    function Objfn1(){
        this.a = 10;
        console.log(this);
    }
    var objfn1 = Objfn1();//此时输出的是对象 Window {stop: function, open: function, alert: function, confirm: function, prompt: function…}
    //console.log('objfn.a='+objfn1.a);//错误,Cannot read property 'a' of undefined

    //三、对象方法
    //如果函数作为对象的方法,方法中的this指向该对象
    var obj={
        a:10,
        foo:function () {
            console.log(this);//Object {a: 10, foo: function}
            console.log(this.a);//10
        }
    }
    obj.foo();
    //注意,要是此时在对象方法中定义函数,那么情况就不同了
    //此时的函数fn虽然是在 obj1.foo1内部定义的,但它仍然属于一个普通的函数,this仍然指向window.
    var obj1={
        a1:10,
        foo1:function () {
            function fn(){
                console.log(this);//Window {stop: function, open: function, alert: function, confirm: function, prompt: function…}
                console.log(this.a1);//undefined
            }
            fn();
        }
    }
    obj1.foo1();
    //另外,如果此时foo2不作为对象方法被调用,则
    var obj2 = {
        x: 10,
        foo2: function () {
            console.log(this);       //Window
            console.log(this.x);     //undefined
        }
    };
    var fn2 = obj2.foo2;
    //等价于fn2 = function () {
        //console.log(this);       //Window
        //console.log(this.x);     //undefined
    //}
    fn2();//此时又是在全局里执行的普通函数。


    //四、构造函数的prototype属性
    //在 Foof.prototype.getX函数中,this 指向的 Foof对象。不仅仅如此,即便是在整个原型链中,this 代表的也是当前Foof对象的值。
    function Foof(){
        this.x = 10;
    }
    Foof.prototype.getX = function () {
        console.log(this);        //Foof {x: 10}
        console.log(this.x);      //10
    }
    var foof = new Foof();
    foof.getX();


    //五、函数用call、apply或者bind调用
    var obja = {
        x: 10
    }
    function fooa(){
        console.log(this);     //Object {x: 10}
        console.log(this.x);   //10
    }
    fooa.call(obja);
    fooa.apply(obja);
    fooa.bind(obja)();
</script>
复制代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值