<font color = "#749DD6">JS中this的指向问题</font>

JS中this的指向问题

0x01 前置知识

| 注意此文章建议有js基础的人阅读

  • var 关键字声明变量时,它是全局声明的,如果在函数内部声明则是局部声明的。let 关键字的行为类似,但有一些额外的功能。 在代码块、语句或表达式中使用 let 关键字声明变量时,其作用域仅限于该代码块、语句或表达式。

  • 顶层对象(浏览器环境中指的是window;Node.js环境中指的是Global)的属性和全局变量属性的赋值是相等价的,使用 var 和 function 声明的是顶层对象的属性,而 let 就属于 ES6 规范了,但是 ES6 规范中 let、const、class 这些声明的虽然是全局变量,但不再属于顶层对象的属性。

  • 首当我们调用一个函数,函数内部的 this 值如何被确定取决于函数是如何被调用的。

    1. 方法调用

      当函数作为对象的属性被调用时(foo.bar()),this 会绑定到那个对象上。

    2. 普通函数调用

      当函数不作为对象的方法被调用时(bar()),在非严格模式下,this 会绑定到全局对象上。在浏览器环境中,全局对象是 window,而在Node.js环境中,全局对象是 global

下面示例,声明函数 foo(),执行 foo.count=0 时,向函数对象 foo 添加一个属性 count。但是函数 foo 内部代码 this.count 中的 this 并不是指向那个函数对象,for 循环中的 foo(i) 掉用它的对象是 window,等价于 window.foo(i),因此函数 foo 里面的 this 指向的是 window。

function foo(num){
  this.count++; // 记录 foo 被调用次数
}
foo.count = 0;
window.count = 0;
for (let i = 0; i < 5; i ++){
    foo(i);
}
console.log(foo.count, window.count); // 0 4

0x02 This的四种绑定规则

默认绑定

当函数调用属于独立调用(不带函数引用的调用),无法调用其他的绑定规则,我们给它一个称呼 “默认绑定”,在非严格模式下绑定到全局对象,在使用了严格模式 (use strict) 下绑定到 undefined。

严格模式下调用

'use strict'
function demo(){
  // TypeError: Cannot read property 'a' of undefined
  console.log(this.a);
}
var a = 1;
demo();

非严格模式下调用

在浏览器环境下会将a绑定到 window.a,以下代码使用var声明的变量a会输出1。

function demo(){
  console.log(this.a); // 1
}
var a = 1;
demo();

根据前面提到的知识如果使用 let 或 const 声明变量 a 则结果会输出 undefined。

隐式绑定

在一个对象的内部通过属性间接引用函数,从而把 this 隐式绑定到对象内部属性所指向的函数。

看以下示例:

function child() {
  console.log(this.name);
}
let parent = {
  name: 'zhangsan',
  child,
}
parent.child(); // zhangsan

隐式绑定的隐患

被隐式绑定的函数,因为一些不小心的操作会导致绑定对象的丢失,此时就会应用最开始讲的绑定规则中的默认绑定,看下面代码(浏览器环境执行):

function child() {
  console.log(this.name);
}
let parent = {
  name: 'zhangsan',
  child: child
}
let parent2 = parent.child;
var name = 'lisi';
parent2();//这种形式的调用属于普通函数调用,所以绑定到window,输出lisi

显示绑定

需要引用一个对象时进行强制绑定调用,js 有提供 call()、apply() 方法

call()、apply() 这两个函数的第一个参数都是设置 this 对象,区别是 apply 传递参数是按照数组传递,call 是一个一个传递。

function fruit(...args){
  console.log(this.name, args);
}
var apple = {
  name: '苹果'
}
var banana = {
  name: '香蕉'
}
fruit.call(banana, 'a', 'b')  // [ 'a', 'b' ]
fruit.apply(apple, ['a', 'b']) // [ 'a', 'b' ]

new 绑定

new 绑定也可以影响 this 调用,它是一个构造函数,每一次 new 绑定都会创建一个新对象。

function Fruit(name){
  this.name = name;
}

const f1 = new Fruit('apple');
const f2 = new Fruit('banana');
console.log(f1.name, f2.name); // apple banana

如果 this 的调用位置同时应用了多种绑定规则,它是有优先级的:new 绑定 -> 显示绑定 -> 隐式绑定 -> 默认绑定。

使用new 调用函数之后,该foo函数才作为构造函数进行调用,构造一个全新的对象赋值给test,而test对象的_proto_
指向了test的prototype对象,test的prototype对象有一个属性constructor,指向test的构造函数foo,这个就是javascript的原型链,也是javascript的特性。对于test对象调用的方法以及属性,会先在test对象进行寻找,如果找到的话,就直接进行调用,寻找停止。如果寻找不到,顺着原型链,在test对象的prototype对象寻找,如果找到的话,进行调用,寻找停止,如果还是找不到,会在prototype对象的原型上进行寻找,这个时候_proto_指向了Object的prototype对象,Object的prototype的constructor指向了Object()本身,而一些Obejct的函数如toString等等就是在这个原型上进行调用的,这个时候如果还是找不到需要属性或者方法,那么就是语法错误,未定义了。因为Object的prototype的_proto_指向是null了,也就没有任何对象属性以及方法了。

0x03 箭头函数

箭头函数并非使用 function 关键字进行定义,也不会使用上面所讲解的 this 四种标准规范,箭头函数会继承自外层函数调用的 this 绑定。

function fruit(){
  return () => {
    console.log(this.name);
  }
}
var apple = {
  name: '苹果'
}
var banana = {
  name: '香蕉'
}
var fruitCall = fruit.call(apple);
fruitCall.call(banana); // 苹果

posted @ 2023-11-03 12:34  Morning|Star  阅读(45)  评论(0)    收藏  举报