【JavaScript——this全面解析】


一、什么是this?

  this是在运行时绑定的,并不是编写时绑定的,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置么有任何关系,只取决于函数的调用方式。
  当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

二、this的绑定规则

1、默认绑定

首先要介绍的最常用的函数调用类型:独立函数调用。其他规则不满足时,启用默认规则:

function foo(){
	console.log(this.a)
}
var a = 2;
foo() // 2

  调用foo()时,this.a被解析成了全局变量a。因为函数调用时应用了默认绑定,因此this指向全局对象。
  怎么知道这里应用了默认绑定呢?可以通过分析调用位置看foo()时如何调用的。在代码中,foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定
  如果使用严格模式,则不能将全局对象用于默认绑定。this会绑定到undefined:

function foo(){
	"use strict";
	console.log(this.a)
}
var a = 2;
foo() // TypeError: this is undefined

"use strict";
function foo2() {
    console.log(this); // undefined
}
foo2();

2、隐式绑定

调用位置位置是否有上下文对象,如果有,谁调用this就指向谁:

var a = 4;
function foo() {
	console.log(this.a);// this指向 obj,foo函数的调用栈是 obj
	console.log(a) // 词法作用域(只与函数书写位置有关),函数书写在全局作用域
}
var obj = {
	a:2,
	foo: foo
};
obj.foo(); //2 4
//隐式绑定规则会把函数调用中的this绑定到这个上下文对象。调用foo()时this被绑定到obj上,所以this.a和obj.a是一样的。

对象属性引用链中只有上一层或者最后一层在调用位置中起作用:

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

隐式丢失是一种常见问题,指隐式绑定的函数会丢失绑定对象
1)例子1:

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

var obj = {
	a: 2,
	foo: foo
}
var bar = obj.foo; //函数别名
var a = "oops, global"; // a 是全局对象的属性
bar(); // oops,global   虽然bar是obj.foo的一个引用,它引用的是foo函数本身,bar是不带任何修饰作用的函数本身。

2)例子2:一种更微妙更常见并且出乎意料的情况发生在传入函数时:
参数传递其实就是一种隐式赋值,传入函数也会被隐式赋值,所以结果跟例1一样。

function foo() {
	console.log(this.a);
}
function doFoo(fn){
	fn(); //调用位置,实际引用是foo
}
var obj = {
	a:2,
	foo:foo
}
var a = "oops,global";
doFoo(obj.foo); //oops,global

// setTimeout函数原理也一样,fn也会隐式丢失
//function setTimeout(fn,delay){
//	fn() 
//}

3、显示绑定

显示绑定就是手动将JS中call()、apply()、bind()方法手动将this绑定到某个对象:

function a(){
    console.log(this.num);
}
obj = {
    num:1
}
var num = 2;
a(); // 2
a.call(obj); // 1
a.apply(obj); // 1
a.bind(obj)(); // 1

显示绑定仍然无法解决丢失绑定的问题,而硬绑定可以解决:

function foo(){
    console.log(this.num);
}
var obj = {
    num:1,
    foo:foo
}
function f(fn){
    // fn(); // 原来的写法
    // 硬绑定
    fn.call(obj)
}
var num = 2;
f(obj.foo); // 1

这种绑定是一种显示的强制绑定,我们称之为硬绑定。后面无论如何调用f()函数,它总会手动在obj上调用foo()函数。

4、new绑定

JS的new机制与其他语言不同,在JS中,构造函数只是一些使用new操作符时被调用的函数。它们只是new操作符调用的普通函数而已。
new操作符发生的操作:

function foo(name) {
	this.name= name
}
//foo为构造函数, args表示传参
function myNew(foo, ...args) {
    // 1.在内存中创建一个新对象
    let obj = {};
    
    // 2.这个新对象会被执行[[ptototype]]连接
    obj.__proto__ = foo.prototype;
    
    // 3.改变this指向:这个新对象绑定到函数调用的this
    let res = foo.apply(obj, args);
    
    // 4.如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
    if (res instanceof Object) {
        return res;
    } else {
        return obj;
    }
}

let obj = myNew(foo, 2);
console.log("newObj:", obj);

new绑定:构造新对象并把新对象绑定到函数调用中的this,例子如下:

function Foo(a) {
    this.num = a;
    this.that = this;
    console.log(this);
}
var bar = new Foo(1); // bar就是默认返回的新对象
console.log(Foo); 
console.log(bar.num); // 1,新对象绑定函数调用的this
console.log(bar); 
console.log(bar === bar.that); //true,  函数中的this,就是新对象bar

三、优先级

new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
1、显示绑定大于隐式绑定例子

function foo(){
    console.log(this.num);
}
var obj = {
    foo:foo,
    num:1
}
var num = 2;
obj.foo(); // 1
obj.foo.call(window); // 2 显示绑定比隐式绑定优先

2、new绑定大于隐式绑定例子

function foo(something){
    this.a = something;
}
var obj = {
    foo:foo
}
obj.foo(2); 
console.log(obj.a)// 2
var bar = new obj.foo(4);
console.log(bar.a)// 4 new 绑定比隐式绑定优先。因为 bar.num的值为3,并没有受到 obj的影响

3、new绑定大于显示绑定的例子:

function foo(something){
    this.a = something;
}
var obj = {}
var bar = foo.bind(obj);
bar(2);
console.log(bar.a); // 2
var baz = new bar(4); // new比显示绑定优先。因为new bar(4)没有受到foo.bind(obj)中obj的影响

console.log(baz.a) // 4
console.log(bar.a) // 2

四、绑定例外

1、如果把null undefined作为this的绑定对象传入apply、bind、call,调用时会被忽略,实际是默认绑定,这时绑定到window

function foo() {
	console.log(this.a)
}
var a = 2;
foo.call(null) // 2

2、更安全的this
当总是使用null或undefined来忽略this可能会产生一些问题,比如一些第三方库中函数使用的this,那么就会默认绑定到window(非严格),这将会导致错误。
所以更安全的做法就是创建一个特殊的空对象,将所有函数的this限制在这个空对象中,不会对全局对象产生任何影响。

function foo(a,b) {
    console.log(a,b);
}
var num = 1;
var obj = {
    num:2,
    foo:foo
}
var ø = Object.create(null);
foo.call(ø,[2, 3]); // undefined 2 3 , this是 {}
setTimeout(obj.foo.bind(ø,'ø'),100); // undefined 'ø' , this是 {}
setTimeout(obj.foo.call(ø,'ø'),100); // undefined 'ø' , this是 {}

3、软绑定
硬绑定这种方式可以把this强制绑定到指定的对象(除了new),防止函数调用应用默认规则。问题在于,硬绑定降低函数灵活性,使用之后就无法使用隐式绑定或者显示绑定修改this。我们可以用软绑定的方式:

Function.prototype.softBind = function (obj) {
    var fn = this;
    var curried = [].slice.call(arguments, 1);
    var bound = function () {
        return fn.apply(
            (!this || this === (window || global)) ? obj : this,
            curried.concat.apply(curried, arguments)
        )

    }
    bound.prototype = Object.create(fn.prototype);
    return bound;
}

软绑定使用

function foo() {
    console.log("name: " + this.name);
}
var obj = {name: 'obj'},
    obj2 = {name: "obj2"},
    obj3 = {name: "obj3"};
var fooOBJ = foo.softBind(obj);
fooOBJ() // name: obj
fooOBJ.call(obj3) // name: obj3----->软绑定,正常应该是obj
obj2.foo = foo.softBind(obj);
obj2.foo() // obj2------>软绑定,正常应该是obj
setTimeout(obj2.foo, 10) // obj----->软绑定
var newFoo = new fooOBJ;
newFoo // undefined

总结

  当函数调用时,会创建执行上下文,this时上下文的一个属性,在函数执行时会用到。this有四条绑定规则,分别是默认绑定、隐式绑定、new绑定、显示绑定。this绑定的判断步骤:1、new绑定?如果是,则this绑定的是新创建的对象。2、call/apply(显示绑定)或者硬绑定调用?是的话,this绑定的指定的对象。3、函数在上下文对象中调用(隐式调用)?是的话,this绑定的是上下文对象。4、以上都不是,则用默认绑定。
(参考书籍:《你不了解的JavaScript》)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值