写在最前面:这是我写的一个一文搞懂JS系列专题。文章清晰易懂,会将会将关联的只是串联在一起,形成自己独立的知识脉络,整个合集读完相信你也一定会有所收获。写作不易,希望您能给我点个赞!
合集地址:一文搞懂JS系列专题
概览
-
食用时间: 10分钟
-
难度: 简单,别跑,看完再走
-
食用价值: 了解改变
this
指向的几种方式。 -
铺垫知识
一文搞懂JS系列(十)之彻底搞懂this指向,理解
this
的设计初衷以及this指向的基本规则。一文搞懂JS系列(十一)之this的三种特殊指向,理解
this
指向在三种特殊情况下的指向问题。一文搞懂JS系列(七)之构造函数,new,实例对象,原型,原型链,ES6中的类,对于第三种,通过
new
改变this
指向有很大的帮助。
温馨提示:在阅读本章节前,最好先阅读以上两篇文章,方便对于箭头函数的 this
指向的理解
方式目录
- 使用 ES6 的箭头函数
- 使用
apply
、call
、bind
-
new
实例化一个对象 let vm = this;
箭头函数
箭头函数是作为 ES6
中的一个特性。
它除了减少代码量,书写优雅等特点之外,还有改变 this
指向的作用。
在 this
的设计之初,它是用来指代当前的运行环境(执行上下文)的。而箭头函数可不是根据调用来决定 this
指向的,而是根据函数定义时的 this
指向。
造成这个现象的主要原因是箭头函数中没有 this
绑定,必须通过查找作用域链来决定其值,所以,箭头函数引用的是外层作用域的 this
。
如果箭头函数被非箭头函数包含,则 this
绑定的是最近一层非箭头函数的 this
,否则,this
为 undefined
。
正是因为它没有 this ,所以也就不能用作构造函数
那么,我们来通过一个案例,来了解 箭头函数的 this
指向。
首先来看一个用普通函数写的例子,
例1:
var dog = {
name: 'wangcai',
hello: function (){
console.log(this.name)
}
}
var name='windowName';
// 调用语句
dog.hello(); // wangcai
因为 this
永远指向最后调用它的那个对象 ,最后调用对象是 dog
,所以,输出的值为 wangcai 。
那么,接下来,我们用箭头函数来改写一下这个例子。
例2:
var dog = {
name: 'wangcai',
// 声明语句
hello: ()=>{
console.log(this.name)
}
}
var name='windowName';
dog.hello(); // windowName
这种时候,我们可以看到,输出的结果为 windowName 。
咦,为什么输出的值是 windowName ,而不是 wangcai 。首先,箭头函数的 this
指向不根据调用语句,而是根据声明语句,这个我理解, dog.hello()
我直接无视。
可 hello
方法不是定义在对象 dog
中的么,可千万不能被它声明的声明位置所欺骗。
还记得上面我们说的,箭头函数根据函数定义时的 this
指向,由于它本身没有 this
绑定,必须通过查找作用域链来决定其值,所以,箭头函数引用的是外层作用域的 this
。
但是对象不构成单独的作用域,所以,此处的 this
指向的是全局对象,也就是 window
。
所以,它的输出值为 windowName。
由于箭头函数的 this
指向固定的特点,很利于封装回调函数。
同时,定义对象方法的时候,我们不适合使用箭头函数。
可以用 ES6
的方法简写来代替:
var dog = {
name: 'wangcai',
hello(){
console.log(this.name);
}
}
上面的代码等同于:
var dog = {
name: 'wangcai',
hello: function(){
console.log(this.name);
}
}
apply 、 call 、 bind
将这三个方法放在一起的主要原因,就是它们都可以改变 this
指向,而且都是函数的方法。
首先要明白一点,它们是函数的一个方法。
所以,应该写在方法的后面,它的主要作用是改变当前方法的 this
指向。
将之补全,其实就是:
Function.prototype.apply()
Function.prototype.call()
Function.prototype.bind()
当然,心急吃不了热豆腐,我们来一一揭开它们的面纱。
不过,为了方便大家的理解,我们先来一个案例:
例3:
var dog = {
name: 'wangcai',
hello(...rest){
console.log(`${this.name} 喜欢吃 ${rest.join(',')}`);
}
}
var name='cooldream'
dog.hello('骨头','肉条'); // wangcai 喜欢吃 骨头,肉条
ES6
引入 rest
参数(形式为 ...变量名
),用于获取函数的多余参数,这样就不需要使用 arguments
对象了。rest
用于将剩余参数打包,以数组的形式返回。
接下来,我们就通过三种方式,通过改变 this
来让 hello()
指向 window
。
apply
Function.apply(thisArg,[argsArray])
apply()
相当于给定方法的 this
指向,并且执行它。
首先第一个参数就是 this
指向,第二个就是方法调用的传参。第二个传参的格式为数组。
那么,我们还是使用 dog.hello()
方法进行调用,但是,不同的一点是,我们要用 apply()
去改变 this
指向,让它指向 window
,输出 cooldream 喜欢吃 牛蛙煲,烤鸡,芝士焗饭
xdm , 上代码:
var dog = {
name: 'wangcai',
hello(...rest){
console.log(`${this.name} 喜欢吃 ${rest.join(',')}`);
}
}
var name='cooldream'
dog.hello.apply(window,['牛蛙煲','烤鸡','芝士焗饭']) //cooldream 喜欢吃 牛蛙煲,烤鸡,芝士焗饭
可以看到,我们用 apply(window)
,通过第一个参数 thisArg
,将 this
指向了 window
。
call
Function.call(thisArg, arg1, arg2, ...)
call()
方法和 apply
的作用,其实是一样的,这里就不再赘述了。
如果忘记了,也不用返回上面继续去看了,因为我觉得你看完这篇文章,一小时也就忘记了。(开个玩笑,不要问为什么加粗,怕你跑了),请自觉返回上层查看,温故而知新。
可以看到,第一个参数和 apply
一模一样,但是,两者的主要区别就在于第二个入参。
它是用 ,
拼接的列表。
下面,我们用 call()
来改写一下。
var dog = {
name: 'wangcai',
hello(...rest){
console.log(`${this.name} 喜欢吃 ${rest.join(',')}`);
}
}
var name='cooldream'
dog.hello.call(window,'牛蛙煲','烤鸡','芝士焗饭') //cooldream 喜欢吃 牛蛙煲,烤鸡,芝士焗饭
bind
bind()
相当于重新创建一个指向你的第一个 this
入参的函数,然后没了,懂吗,它不会执行啊。
那还能怎么办呢,记得自己调用。
别写了之后,咦,我的代码怎么不执行呢???
var dog = {
name: 'wangcai',
hello(...rest){
console.log(`${this.name} 喜欢吃 ${rest.join(',')}`);
}
}
var name='cooldream'
dog.hello.bind(window,'牛蛙煲','烤鸡','芝士焗饭')() //cooldream 喜欢吃 牛蛙煲,烤鸡,芝士焗饭
var dog = {
name: 'wangcai',
hello(...rest){
console.log(`${this.name} 喜欢吃 ${rest.join(',')}`);
}
}
var name='cooldream'
dog.hello.bind(window,['牛蛙煲','烤鸡','芝士焗饭'])() //cooldream 喜欢吃 牛蛙煲,烤鸡,芝士焗饭
不难发现,对于入参,列表和数组,都可以接受。
那么,总结一下:
-
作用
call
apply
bind
都可以改变this
的指向。 -
入参
call
和apply
的功能是一样的,但是入参的方式不一样。call
接受一串用,
拼接的列表。apply
接受数组。bind
表示都行,数组和列表都可。 -
执行机制
call
和apply
都是立即执行的。bind
是创建一个新的函数,所以本身并不会执行,需要自己进行调用。
new
构造函数中的 new
能改变 this
指向,其实本身和 new
本身做了什么有关。
理解 new
的实现方式,也就不难理解了。
这次上个来点难度的,自己实现 new
,出息了,自己造轮子。
那么,我们就来说一说 new
操作符,到底做了哪些事情
- 创建一个新的对象
- 将空对象的原型地址
_proto_
指向构造函数的原型对象 (这里涉及到的原型和原型链的概念,下面会有讲到)- 利用
apply
,call
, 或bind
,将原本指向window的绑定对象this指向了obj。(这样一来,当我们向函数中再传递实参时,对象的属性就会被挂载到obj上。)- 返回这个对象
那么,接下来我们可以自己实现一个 new
方法
// const xxx = _new(Person,'cooldream',24) ==> new Person('cooldream',24)
function _new(fn,...args){
// 新建一个对象 用于函数变对象
const newObj = {};
// 将空对象的原型地址 `_proto_` 指向构造函数的原型对象
newObj.__proto__ = fn.prototype;
// this 指向新对象
fn.apply(newObj, args);
// 返回这个新对象
return newObj;
}
其实,关于自己实现 new
。
用 apply
call
或者 bind
都可以,看心情。装完逼的感觉,舒服。
如有不懂,可以移步至这里,一文搞懂JS系列(七)之构造函数,new,实例对象,原型,原型链,ES6中的类 ,完整了解 构造函数 , new
,实例对象 ,原型 ,原型链 ,以及 ES6
当中的类,这些大家老生常谈的概念之间的关系。
所以,这就是为什么,new
同样可以做到改变 this
指向。
let vm = this;
说这个改变 this
指向,虽然有点牵强了。但是,也不失为一种方法。
相当于在函数的外层,定义一个变量 vm
,来保存当前环境中的 this
。
然后,内层函数如果想访问最外层的 this
指向,仅仅只需要用 vm
即可。
因为,函数是可以创建单独的作用域的。而作用域是可向外访问不可向内访问的。就是我们之前讲过的探出头去。
这样子,就属于无脑操作,我管它什么 this
指向规则,我只知道,vm
一定是最外层的 this
。无论在嵌套多深的函数内访问 vm
,vm
始终都是最外层的 this
。
以上就是我们所有的关于 this
指向的内容。
系列目录
-
一文搞懂JS系列(一)之编译原理,作用域,作用域链,变量提升,暂时性死区
-
一文搞懂JS系列(二)之JS内存生命周期,栈内存与堆内存,深浅拷贝
-
一文搞懂JS系列(三)之垃圾回收机制,内存泄漏,闭包
-
一文搞懂JS系列(四)之闭包应用-柯里化,偏函数
-
一文搞懂JS系列(五)之闭包应用-防抖,节流
-
一文搞懂JS系列(六)之微任务与宏任务,Event Loop
-
一文搞懂JS系列(七)之构造函数,new,实例对象,原型,原型链,ES6中的类
-
一文搞懂JS系列(八)之轻松搞懂Promise
-
一文搞懂JS系列(九)之更优雅的异步解决方案
-
一文搞懂JS系列(十)之彻底搞懂this指向
-
一文搞懂JS系列(十一)之this的三种特殊指向
网友评论