this全面解析

this 深度解析


最近,重温曾探的《JavaScript设计模式与开发实践》【下载相关学习资料 】 在各种案例中,关于this、call、apply 的使用及奇平凡,有些设计模式的案列相对复杂,各种来回复杂的调用,让人有点丈二的和尚摸不着头脑,于是又重温一下this,这里我会由浅入深的对自己再次的学习进行一些总结。

this 指向

引用《javascript高级程序设计第三版》【下载相关学习资料 】中的官方解析。this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。(简单来说就是:谁调用就指向谁)

this 的指向大致可以分为以下 4 种:

  • 作为对象的方法调用。
  • 作为普通函数调用。
  • 构造器调用。
  • Function.prototype.call 或 Function.prototype.apply 或 Function.prototype.bind 调用
  • 箭头函数调用
  • DOM对象的处理函数
  • 原型链中的this

先上基础案列

作为对象的方法调用


当函数作为对象的方法被调用时, this 指向该对象

var obj = {
    a: 1,
    getA: function(){
        alert ( this === obj ); // 输出: true
        alert ( this.a ); // 输出: 1
    }
};
obj.getA();

这里的getA()被obj这个对象调用,所以getA()内部中的this指向obj

作为普通函数调用


最为普通函数方式,此时的 this 总是指向全局对象
方式1:下面我称之为fun1

window.name = 'window';
var getName = function(){
    return this.name;
};
console.log( getName() ); // 输出: globalName

方式2:下面我称之为fun2

window.name = 'window';
var myObject = {
    name: 'sven',
    getName: function(){
        return this.name;
    }
};
var getName = myObject.getName;
console.log( getName() ); // globalName

观察上面两种方式,fun1 和 fun2 的区别,fun2和 作为对象的方法调用 的唯一区别是将 myObject.getName 先赋值给变量 getName,然后再调用,这种写法其实就是下面的代码形式:

window.name = 'window';
var getName = function(){
        return this.name;
    };
console.log( getName() ); // globalName

和 fun1 一样,所以就一目了然了

作为构造器调用


当用 new 运算符调用函数时,该函数总会返回一个对象,通常情况下,如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,构造器里的 this 就指向返回的这个对象,

var MyClass = function(){
    this.name = 'sven';
    return 'anne'; // 返回 string 类型
};
var obj = new MyClass();
console.log(obj) // {name: 'sven'}
alert ( obj.name ); // 输出: sven

这里顺便讲一下为啥是这样,或者说 new MyClass 到底做了什么?

var MyClass = function(){
    this.name = 'sven';
};
// new 的四步分别做了什么
var newFun = function() {
  var obj = new Object(); // 创建一个对象
  var fn  = Array.prototype.shift.call(arguments);
  obj.__proto__ = fn.prototype; // 将新对象的原型指向构造函数的原型
  fn.call(obj); // 改变 this 的作用域
  return obj // 返回新对象
}
newFun(MyClass)

但用 new 调用构造器时,还要注意一个问题,如果构造器显式地返回了一个显示的 object 类型的对象,那么此次运算结果最终会返回这个对象,而不是我们之前期待的 this:

var MyClass = function(){
    this.name = 'sven';
    return { // 显式地返回一个对象
        name: 'anne'
    }
};
var obj = new MyClass();
cosole.log( obj); // 输出: {name: 'anne'}
  • 这里很明显不是我们想要的 {name: ‘sven’}, 这又是为啥,又是怎么实现的?
  • 在刚才的代码基础上,我们再稍作改动
var MyClass = function(){
    this.name = 'sven';
     return { // 显式地返回一个对象
        name: 'anne'
    }
};
// 内部返回对象是如何进行判断的
var newFun = function() {
  var obj = new Object(); // 创建一个对象
  var fn  = Array.prototype.shift.call(arguments);
  obj.__proto__ = fn.prototype; // 将新对象的原型指向构造函数的原型
  /*********以下为修改代码**********/
  var innertObj = fn.call(obj); // 改变 this 的作用域
  // 判断函数内部是否返回了一个对象
  if (innertObj && (innertObj instanceof Object)) { 
    return innertObj
  }
  /*********以上为修改代码**********/
  return obj // 返回新对象
}
newFun(MyClass)

call、apply、bind

call、apply 方法

call 和 apply 可以动态地改变传入函数的 this:

var obj1 = {
    a: 1,
    b: 2,
    caculate: function(c,d){
        return this.a + this.b + c + d;
    }
};
var obj2 = {
    a: 10,
    b: 20,
};
console.log( obj1.caculate(3,4) ); // 输出: 10
console.log( obj1.caculate.call( obj2, 3, 4 ) ); // 输出: 37
console.log( obj1.caculate.apply( obj2, [30, 40] ) ); // 输出: 100

call 和 apply 的用法唯一的不同就是传参的方式不同,参见上面两种调用方式

bind 方法

bind 也可以动态地改变传入函数的 this,但是Ta是永久绑定,一旦绑定不会在改变:
?:还是用上面的例子

// 调用方法1
var caculate = obj1.caculate
var fun1 = caculate.bind({a: 44, b: 5})(3,4)
var fun2 = caculate.bind({a: 22, b: 55})(3,4)
console.log(fun1()) // 56
console.log(fun2()) // 84
// 调用方法2
var fun3 = caculate.bind({a: 44, b: 5})(3,4) // fun3 被绑定
var fun4 = fun3.bind({a: 22, b: 55})(3,4) // fun4 改变 fun3 的作用域
console.log(fun1()) // 56
console.log(fun2()) // 56

思考???

  • 方法 2 中的 fun4 再次改变 fun3 的作用域并没有生效?
  • 作为一个合格程序员,我会有疑问,那问啥上面 fun1 和 fun2 中的 caculate 没有永久绑定到 {a: 44, b: 5} 中呢???

原因其实很简单,那是因为 caculate 是没有使用bind来改变其作用域的,谁用bind绑定后,谁的作用域就永久生效,不会再被改变

为了进了一步给小伙伴们证实一下,请看下面的?:
var obj1 = {
    a: 1,
    b: 2,
    caculate: (function(c,d){
        return this.a + this.b + c + d;
    }){a: 100, b: 200} // 手动强行绑定
};
var caculate = obj1.caculate
var fun1 = caculate.bind({a: 44, b: 5})
var fun2 = caculate.bind({a: 22, b: 55})
console.log(fun1(3,4)) // 307
console.log(fun2(3,4)) // 307

箭头函数

官方是这么解释的: 在箭头函数中,this与封闭词法环境的this保持一致。在全局代码中,它将被设置为全局对象
表示每台看懂,肿么办,撸起袖子就是干啊,看?:

var a = 'window'
var obj1 = {
    a: 1,
    b: 2,
    caculate1:() => {
		console.log(this.a) // this 从 window 中引用
    },
  	caculate2: function() {
		return () => console.log(this.a) // caculate2 和 obj1 构成了封闭的词法环境,此时的 链式调用,this 指向 obj1
    },
	bar: function() {
    	console.log(this.a) // 同上
  	}
};
obj1.caculate1() // window
obj1.caculate2()() // 1
obj1.bar() // 1

要弄明白为什么是这样的打印结果,我们得追本溯源,箭头函数自身是没有 this 关键字的,而是从外部通过链式引用而来的 只要明白这一点就不难解释上面的输出结果了

DOM事件的处理函数

当函数被用作事件处理函数时,它的this指向目标元素

// 被调用时,将关联的元素变成蓝色
function bluify(e){
  console.log(this === e.currentTarget); // 总是 true
  // 当 currentTarget 和 target 是同一个对象时为 true
  console.log(this === e.target);        
  this.style.backgroundColor = '#A5D9F3';
}
// 获取文档中的所有元素的列表
var elements = document.getElementsByTagName('*');
// 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色
for(var i=0 ; i<elements.length ; i++){
  elements[i].addEventListener('click', bluify, false);
}

当代码被内联on-event 处理函数调用时,它的this指向监听器所在的DOM元素:

<button onclick="alert(this.tagName.toLowerCase());">
  Show this
</button>

上面的 alert 会显示button。注意:只有外层代码中的this是这样设置的:

<button onclick="alert((function(){return this})());">
  Show inner this
</button>

原型链中this

var o = {
  f: function() { 
    return this.a + this.b; 
  }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5

不做过多解释了,其实本质和对象调用是一样的,只不过 f() 是继承而来,仅此而已,我们只需要记住,谁调用就指向谁是没毛病的

案例:

var a=3;
var myObject = {
  foo: "bar",
  func: function() {
    var self = this;
    console.log("outer func:  this.foo = " + this.foo);  // bar
    console.log("outer func:  self.foo = " + self.foo);  // bar
    (function() {
      console.log("inner func:  this.foo = " + this.a); // 3  这里的this是当作普通函数调用的,所以指向全局
      console.log("inner func:  self.foo = " + self.foo);  // bar  self 其实就是 myObject, 你可以理解被绑定了
      console.log.bind(self, "inner func:  self.foo = " + self.foo);
    }());
  }
};
myObject.func();

思考一下?

var a=3;
var myObject = {
  foo: "bar",
  func: function() {
    var self = this;
    console.log("outer func:  this.foo = " + this.foo);  // bar
    console.log("outer func:  self.foo = " + self.foo);  // bar
    (function() {
      console.log("inner func:  this.foo = " + this.a); // 3  这里的this是当作普通函数调用的,所以指向全局
      console.log("inner func:  self.foo = " + self.foo);  // bar  self 其实就是 myObject, 你可以理解被绑定了
      console.log.bind(self, "inner func:  self.foo = " + self.foo);
    }());
  }
};
var fun = myObject.func;
fun() // 又会输出什么

小结

关于this的使用场景以及相关的原理总结到这里,后续会持续跟进一些前端必须掌握的知识

其它前端性能优化:

前端技术架构体系(没有链接的后续跟进):

其它相关

欢迎各位看官的批评和指正,共同学习和成长
希望该文章对您有帮助,你的 支持和鼓励会是我持续的动力

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值