深入剖析JavaScript中的this(下)

五、事件处理函数的this

5.1 事件绑定

<button id="btn">点击我</button>


function handleClick(e) {
  console.log(this);
  // <button id="btn">点击我</button>
}

document.getElementById('btn').addEventListener('click', handleClick, false); 
// <button id="btn">点击我</button>

document.getElementById('btn').onclick = handleClick; 
// <button id="btn">点击我</button>

根据上述代码我们可以得出:当通过事件绑定来给DOM元素添加事件,事件将被绑定为当前DOM对象。

5.2 内联事件

<button onclick="handleClick()" id="btn1">点击我</button>
<button onclick="console.log(this)" id="btn2">点击我</button>

function handleClick(e) {
  console.log(this); // window
}

图片

根据上述代码我们可以得出:点击btn1打印window对象,this指向window;点击btn2打印当前DOM对象,this指向当前DOM对象。

浏览器有三种添加事件监听的方式。
直接在标签内写 οnclick=“fn”,此时this指向window;

在js中 el.οnclick=fn,此时this指向el;

在js中用 el.attachEvent或者el.addEventListener(),此时this指向el。

六、定时器中的this

先看下面的两段代码,看看有何不同之处?

function foo() {
  setTimeout(function() {
    console.log(this); // window
  }, 1000)
}

foo();
var name = "前端技术营";
var obj = {
  name: "张三",
  foo: function() {
    console.log(this.name); // 张三
    setTimeout(function() {
        console.log(this.name); // 前端技术营
    }, 1000)
  }
}

obj.foo();

通过这两段代码发现,函数 foo 内部this指向为调用它的对象,即为obj 。而定时器中的this指向为 window。

如果没有特殊指向,setInterval和setTimeout的回调函数中this的指向都是window。这是因为JS的定时器方法是定义在window下的,但是平时很多场景下,都需要修改this的指向,那么有什么办法让定时器中的this跟包裹它的函数绑定为同一个对象呢?

(1)利用闭包,在外部函数中将this存为一个变量,回调函数中使用该变量,而不是直接使用this。

var name = "前端技术营";
  
var obj = {
  name: "张三",
  foo: function() {
    console.log(this.name); // 张三
    var that = this;
    setTimeout(function() {
        console.log(that.name); // 张三
    }, 1000)
  }
}

obj.foo();

(2)使用bind实现。

var name = "前端技术营";
  
var obj = {
  name: "张三",
  foo: function() {
    console.log(this.name); // 张三
    setTimeout(function() {
        console.log(this.name); // 张三
    }.bind(this), 1000)
  }
}

obj.foo();

七、箭头函数中的this

在使用普通函数之前对于函数的this绑定,需要根据这个函数如何被调用来确定其内部this的绑定对象。而且常常因为调用链的数量或者是找不到其真正的调用者对 this 的指向模糊不清。在箭头函数出现后其内部的 this 指向不需要再依靠调用的方式来确定。

箭头函数不绑定 this ,它只会从作用域链的上一层继承 this。箭头函数中的this是定义函数的时候绑定,而不是在执行函数的时候绑定。箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。

看下面代码来对比普通函数与箭头函数中的this绑定。

var obj = {
  foo: function() {
    console.log(this); // obj
  },
  bar: () => {
    console.log(this); // window
  }
}

obj.foo();
obj.bar();

上述代码中,同样是通过 obj. 方法调用一个函数,但是函数内部this绑定确是不同,只因一个数普通函数一个是箭头函数。

箭头函数中的this继承于作用域链上一层对应的执行上下文中的this。

var name = '前端技术营';
var obj = {
  name: '张三',
  foo: () => {
    console.log(this.name); // 前端技术营
  }
}
obj.foo();

this是继承于作用域链上一层执行上下文的this,这里的箭头函数中的this.name,箭头函数本身与foo平级是key:value的形式,也就是箭头函数本身所在的对象为obj,而obj的上一层执行上下文就是window,因此这里的this.name实际上表示的是window.name,因此输出的是"前端技术营"。

var obj = {
  foo: function() {
    console.log(this); // obj
    var bar = () => {
      console.log(this); // obj
    }
    bar();
  }
}
obj.foo();

在普通函数中,bar 执行时内部this被绑定为全局对象,因为它是作为独立函数调用。但是在箭头函数中呢,它却绑定为 obj 。跟父级函数中的 this 绑定为同一对象。

var obj = {
  foo: () => {
    console.log(this); // window
    var bar = () => {
        console.log(this); // window
    }
    bar();
  }
}
obj.foo();

这个时候怎么又指向了window了呢?当我们找bar函数中的this绑定时,就会去找foo函数中的this绑定。因为它是继承于它的。这时 foo 函数也是箭头函数,此时foo中的this绑定为window而不是调用它的obj对象。因此 bar函数中的this绑定也为全局对象window。

箭头函数的 this 是从当前箭头函数逐级向上查找 this,如果找到了,则作为自己的 this 指向,如果没有则继续向上查找。而父级的 this 是可变的,所以箭头函数的 this 也可跟随父级而改变。

var name = "前端技术营";

function Foo(val) {
  let bar = new Object();
  bar.name = val;
  bar.getVal = () => {
    console.log(this);
  };
  return bar;
}
let baz = new Foo("李四");
baz.getVal(); // Foo {}

八、return中的this

先看一段代码:

function Foo() {  
  this.name = '张三';  
  return {};  
}
var obj = new Foo();  
console.log(obj.name); //undefined

再看一段代码:

function Foo() {  
  this.name = '张三';  
  return function(){};
}
var obj = new Foo();  
console.log(obj.name); //undefined

再看一段代码:

function Foo() {  
  this.name = '张三';  
  return '1';
}
var obj = new Foo();  
console.log(obj.name); // 张三

再看一段代码:

function Foo() {  
  this.name = '张三';  
  return undefined;
}
var obj = new Foo();  
console.log(obj.name); // 张三

通过上面的四段代码会发现什么?如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象,那么this还是指向函数的实例。

还有一点就是虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊。

function Foo() {  
  this.name = '张三';  
  return null;
}
var obj = new Foo();  
console.log(obj.name); // 张三

九、被忽略的this

规则总有例外,这里也一样。如果你把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,应用的是默认绑定规则。

var name = '张三';

function foo() {
  // 张三,this指向window
  console.log(this.name); 
}
var obj = {
  name: '李四'
}
foo.call(null);

什么情况下会传入null呢?

function foo(a, b) {
  console.log("a:" + a + ", b:" + b);
}
// 把数组 “展开” 成参数
foo.apply(null, [2, 3]);

// 使用 bind() 进行柯里化 (预先设置一些参数)
var bar = foo.bind(null, 2);
bar(3);

如果函数不关心 this 的话,你仍然需要传入一个占位值,这时 null 肯定是一个不错的选择,如果某个函数确实使用了 this,默认规则把 this 绑定到全局对象,这将导致不可预计的后果,比如修改全局对象。

十、this绑定优先级

显示绑定:通过使用.call()、.apply()或者.bind()等方法来明确指定函数内部的this值。这样就能将函数与特定的上下文进行绑定。

隐式绑定:当 函数引用 有 上下文对象 时,如 obj.foo() 的调用方式,foo() 内的 this 指向 obj。也就是说,谁调用函数,函数内的 this 就指向谁(无论是普通对象、还是全局对象),this 永远指向最后调用它的那个对象(不考虑箭头函数)。

默认绑定:表现为一个独立函数的直接调用,函数调用时无任何调用前缀的情景。

new绑定:new绑定就是通过new一个构造函数的方式进行绑定,this会指向被new出来的那个对象。

我们首先来看下隐式绑定和显示绑定哪个优先级更高。

function foo() {
  console.log(this.name)
}
var obj1 = {
  name: '张三',
  foo: foo
}
var obj2 = {
  name: '李四',
  foo: foo
}

obj1.foo(); // 张三
obj2.foo(); // 李四

obj1.foo.call(obj2); // 李四
obj2.foo.call(obj1); // 张三

这段到面可以看到,显示绑定优先级比隐式绑定更高。

现在我们需要搞清楚 new 绑定和隐式绑定的优先级谁高。

function foo(name) {
  this.name = name
}
var obj1 = {
  foo: foo
}
var obj2 = {}

obj1.foo('张三');
console.log(obj1.name); // 张三

obj1.foo.call(obj2, '李四');
console.log(obj2.name); // 李四

var bar = new obj1.foo('王五');
console.log(obj1.name); // 张三
console.log(bar.name); // 王五

这段到面可以看到 new 绑定比隐式绑定优先高,但是 new 绑定和显示绑定谁的优先级高呢?

function foo(name) {
  this.name = name
}
var obj1 = {}

var bar = foo.bind(obj1);
bar('张三');
console.log(obj1.name); // 张三

var baz = new bar('李四');
console.log(obj1.name); // 张三
console.log(baz.name); // 李四

new 修改了硬绑定调用 bar() 中的 this。

this 存在很多使用场景,当多个场景同时出现时,就要根据优先级来判断 this 的指向。优先级:new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定。

总结

  1. 定义在全局作用域中的普通函数中的this指向window对象: 作为函数调用,作为内部函数调用
    setTimeout,setInterval。

  2. 事件处理函数中的this指向触发事件的标签元素 DOM对象绑定事件。

  3. 构造函数中的this指向当前正在创建的对象(严格模式下必须使用new)作为构造函数去调用,即new的时候,指向构造函数创建的那个实例 。

  4. 对象方法中的this指向:在使用对象方法调用方式时,this被自然绑定到该对象。

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值