关注

JavaScript原型与原型链深度解析:继承复用与避坑指南

你是不是写函数时总搞不清 this 指向谁?明明在对象里定义的方法,调用时 this 就变成 window 了?或者用回调函数时,想访问外层的 this 却拿不到,只能用 var that = this 这种 “土办法”?

这事儿的核心其实是两个东西:作用域链和 this 绑定规则。之前聊变量提升时提过作用域,但没细说它和 this 的关系,今天咱们就把这俩 “难兄难弟” 掰扯明白,以后再遇到 this 相关的 bug,保准你一分钟定位问题。

先复习下作用域链,这是理解 this 的基础。简单说,作用域链就是函数查找变量的 “路线图”。当函数要访问一个变量时,会先在自己的局部作用域里找,找不到就去定义时的外层作用域找,一层一层往上爬,直到全局作用域,再找不到就报 ReferenceError。

比如这段代码:

let globalVar = "全局变量";

function outer () {

let outerVar = "外层变量";

function inner () {

console.log (outerVar); // 能找到外层变量

console.log (globalVar); // 能找到全局变量

}

inner ();

}

outer ();

inner 函数的作用域链就是:inner 局部作用域 → outer 作用域 → 全局作用域。但作用域链管的是变量查找,this 管的是 “函数调用时的上下文”,两者完全不是一回事,很多人就是把这俩搞混了才踩坑。

小索奇认为,搞懂 this 的关键就一个:this 的指向不是在定义时决定的,而是在调用时决定的。谁调用了函数,this 就指向谁(特殊情况除外)。咱们分最常见的 4 种场景一个个说。

第一种,默认绑定。就是函数直接调用,没有任何前缀,这时候 this 默认指向全局对象(浏览器里是 window,Node 里是 global)。比如:

function showThis () {

console.log (this); // 指向 window

}

showThis ();

但如果在严格模式下,默认绑定会失效,this 会变成 undefined:

"use strict";

function strictShow () {

console.log (this); //undefined

}

strictShow ();

这是个很容易忽略的点,有时候代码在非严格模式下好好的,加了严格模式就报错,很可能是 this 变成 undefined 导致的。

第二种,隐式绑定。函数通过对象的属性调用,比如 obj.fn (),这时候 this 就指向这个对象。比如:

const person = {

name: "小索奇",

sayName () {

console.log (this.name); // 指向 person,打印 “小索奇”

}

};

person.sayName ();

但隐式绑定有个巨坑:函数赋值后调用,this 会丢失。比如:

const say = person.sayName;

say (); // 此时 this 指向 window,打印 undefined

因为 say 变量只是拿到了函数本身,调用时没有对象前缀,就触发了默认绑定。很多人在给 DOM 事件绑定方法时会踩这个坑,比如把对象的方法直接传给 onclick,调用时 this 就变成了 DOM 元素,不是原来的对象。

第三种,显式绑定。就是用 call、apply、bind 这三个方法强制指定 this 的指向,这也是解决 this 丢失的常用办法。比如刚才的例子,用 bind 就能固定 this:

const boundSay = person.sayName.bind (person);

boundSay (); // 打印 “小索奇”

call 和 apply 的区别是传参方式不同,call 是逐个传参,apply 是传数组:

function add (a, b) {

console.log (this.name + "计算:" + (a + b));

}

add.call (person, 1, 2); // 小索奇计算:3

add.apply (person, [1, 2]); // 小索奇计算:3

bind 和它们的区别是,bind 不会立刻执行函数,而是返回一个绑定了 this 的新函数,call 和 apply 会立刻执行。

第四种,new 绑定。用 new 关键字调用构造函数时,this 会指向新创建的实例对象。比如:

function Person (name) {

this.name = name; //this 指向 new 出来的实例

}

const p = new Person ("即兴小索奇");

console.log (p.name); // 即兴小索奇

new 操作做了四件事:创建空对象、把 this 指向空对象、执行构造函数代码、返回这个对象。记住这四步,就懂 new 绑定的原理了。

除了这四种,ES6 的箭头函数是个 “异类”—— 它根本没有自己的 this。箭头函数的 this 是在定义时就绑定好的,继承自外层作用域的 this,之后不管怎么调用,this 都不会变。

比如解决回调函数里的 this 丢失问题,用箭头函数就特方便:

const obj = {

data: [1, 2, 3],

process () {

// 普通回调函数,this 指向 window

this.data.forEach (function (item) {

console.log (this.data); //undefined,this 丢失了

});

// 箭头函数,this 继承外层的 obj

this.data.forEach (item => {

console.log (this.data); // [1,2,3],正确

});

}

};

obj.process ();

小索奇在写 React 类组件时,经常用箭头函数定义方法,就是为了避免手动绑定 this,比如:

class MyComponent extends React.Component {

// 箭头函数自动绑定 this 为组件实例

handleClick = () => {

this.setState ({ count: 1 });

};

render () {

return 点击;

}

}

这比在 constructor 里写 this.handleClick = this.handleClick.bind (this) 清爽多了。

总结下 this 的绑定优先级:new 绑定 > 显式绑定(call/apply/bind)> 隐式绑定 > 默认绑定。记不住的话,遇到 this 问题就按这个顺序排查,先看是不是 new 调用,再看是不是显式绑定,以此类推。

说到这儿,你是不是也踩过 this 的坑?比如在 setTimeout 回调里用 this 访问不到对象属性,或者把对象方法传给事件监听后 this 变了?

最后留个小问题:下面这段代码里,最后打印的 this.name 是啥?

const person = {

name: "小索奇",

sayName: () => {

console.log (this.name);

}

};

const anotherPerson = { name: "即兴小索奇" };

person.sayName.call (anotherPerson);

提示一下,箭头函数的 this 是绑定死的哦!欢迎在评论区写下你的答案~

我是【即兴小索奇】,点击关注,后台回复 领取,获取更多相关资源

转载自CSDN-专业IT技术社区

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/m0_64880608/article/details/151759181

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--