ES6箭头函数的this彻底理解
shell ES6标准新增了一种新的函数:Arrow Function(箭头函数)。
基础语法
通常函数的定义方法
var fn1 = function(a,b) {
return a+ b
}
function fn2 (a,b) {
return a+ b
}
ES6箭头函数语法定义函数,将原函数的“function”关键字和函数名都删掉,并使用“=>”连接参数列表和函数体。
// 把function省掉 用=>连接(参数列表)=>{函数体}
var fn1 = (a,b) => {
return a + b
}
// function省掉 函数名 fn2省掉
(a, b) => {
return a+b
}
1.当函数参数只有一个,括号可以省略;
2.无参数时括号不能省略。
3.多个参数括号不能省略
4.可变参数
// 无参-括号不可以省略
var fn1 = function() {}
var fn1 = () => {}
// 单个参数-括号可以省略
var fn2 = function (a) {}
var fn2 = a => {}
//多个参数
var fn3 = function (a,b) {}
var fn3 = (a,b) => {}
// 可变参数
var fn4 = function (a,b,...args) {}
var fn4 = (a,b,...args) => {}
箭头函数有两种格式。
- 1.一种只包含一个表达式- 省掉了{...}和return。
- 2.还有一种可以包含多条语句-不能省掉{...}和return。没有return返回值就是undefined。
//1.一种只包含一个表达式- 省掉了{...}和return。
(a,b) => a + b
() => return 'hello'//错误,理由如下
/*
return 得加大括号
let a = () => {
return 'hello world'
}
var fn2 = () =>'hello';
这么写的时候 省略花括号的时候
是默认自带了一个return的
省略了花括号的时候 编译器是会给隐含加个return的,
这个时候你再加个return
实际上就相当于有两个return了 就错误了
*/
还可以这样用
箭头函数 可以改变this指针的作用域
-
计算机的所有数据,都是存放在内存的的
-
但是内存不是公共的
-
内存会划分为一个一个的块
-
然后一个进程用一个块
-
同个块里面的 是一个作用域
-
不同块之间是不能访问的
-
js 是单线程的
-
也就是一整个js 是占用一个内存块的
-
但是 一整个内存块 js 拿到之后,并不是全部都通用的
-
js 的进程,现在主流的游览器是谷歌的v8引擎
-
js 是运行在v8上的 这个v8 会将这个内存块,按照js的代码再进行分块
-
一个块内一个作用域
-
所有情况下 js 一个文件先是一个大的作用域
-
我们在dhtml 里面插入 一个 的时候 不同标签之间可以分开,但是都是在同个html里面
-
也就是公用一个大的作用域(但是 对应的node 里面,一个node.js文件是一个作用域)
-
作用域的作用,是用来指针寻址用的,以及用let 定义的
-
作用域、指针、内存的地址的、这些都是逻辑概念。
-
比如我们说的 在内存上是个块,物理上并不是一个块,物理上计算机有一个独特的内存处理算法 是可能分散的
-
但是对于面对我们程序员的时候,计算机会将其封装好,让程序员感觉像是个块
-
这些个内存物理的处理算法,可以见 计算机组成原理 操作系统原理等,但是这里我们先简化理解
-
逻辑上 我们在最外面的时候 这个this 指代的是放置这个script的区块
-
也就是 this并不是指这个灰色的作用域(而是外面的那一层白色的,最底层-自上的一层)
-
而是外面的那一层白色的,最底层
-
所以我们在最外面用this的时候 会指向 window 这个全局
-
因为 window是最底层的
-
当我们在定义一个obj对象的时候,这时 花括号里面的this 还是指代外面的 还没有改变
-
为什么往下看
-
我们在这个obj里面 再嵌套定义一个this 根据指到上面原则
-
第一个不在嵌套的this 会指向灰色的作用域,但是灰色作用域本身不是对象,就会往上指到白色-也就是window
-
第二个,往上指就指到了橙色
-
下再放个function 会再次改变
-
给这个function里面的this标明黑色
-
是因为 function里面的this 现在是没有指代物的 只是放着。
-
为什么function的this是没有指代的呢 (这是因为内存设计,a:{} 是个对象实体,是直接在obj这个块内的内存占用的)
-
但是 function不是,function只是个指针
-
因为 function的代码一般都很大,占用的内存块很大
-
所以 内存划分上 会将function跟obj分开放
-
这样子可以节省obj的内存
-
因为function是一整个连续的大块
-
所以我们将function放出去会性能会更好点
-
然后用个指针b指向这个方向
-
这个指针b 就是我们定义的时候 b:function(){}
-
或者var b = function(){}这个时候b都是个指针
-
(js虽然表面上没有变量类型,但是 实际上还是有的,只是模糊掉了)
-
下面我们将this也写进来
-
聪明的你会发现
-
function的this 不是可以直接指向obj吗?
-
这样子不就解决 这一些作用域问题了?(this是个指针)
-
实际上是 这个this是可以指向obj的,但是js没有这样子设计
-
在别的一些编程语言里面就有这样子的设计,比如C++ 什么的,但是 js没有
(js最早呢sun公司里面,搞java的几个程序员设计的,他们最早的设计可能是为了简化java,在java里面就应该有这样子的设计,那样子的话 如果将this指向obj,
那我如果这个function 不是写在obj里面的
是直接定义的function呢,那样子的话this又该默认指向哪里?
所以 设计人员可能是出于简化也有可能是出于别的目的,这个我也不知道当时他们的想法
总之,他们将js的function里面的this 设计成了指向上下文
也就是内存堆栈里面的上一个作用域
这个就是为什么我们开头要说内存,内存的底层 实际上并不是像数组那样子直接操作的
大多数时候 进程是放到内存的一个堆栈进行操作的
堆栈是个数据结构的概念 我简单介绍一下就是这样子
-
像往一个篮子放书一样
-
每次只能放在最上面,然后再取最上面的,我虽然没有读过v8的源码,但是我猜测,js的代码访问是这样的,
-
我们obj.b的时候,先入obj 然后再入b,然后发现后面没有东西了,就再弹出b
-
先进先出
-
然后b是个函数 下面就执行b的代码
-
执行的时候 遇到了this,就会在内存上去查上一个,结果是obj
-
所以 执行b的时候,this指向了obj
-
因为我们是 obj.b 这个时候 function里面的this就会指向obj。
现在来看
- 所以 为什么我们定义一个function
- school.a = function
- obj.b = function
<script>
const Maid = {
name: "陆",
whereAreYou: () => {
console.log("我在${this.name}")
},
whatIsYourName: function() {
console.log('我是${this.name}');
}
}
function School() {
let School = {
name: "学校"
};
School.a = Maid.whatIsYourName;
/*
相当于School = (
let School = {
name: "学校"
};
a: function() {
console.log('我是${this.name}');
}
)
*/
School.a()
//学校,因为虽然赋值了Maid.whatIsYourName,给他这个function而已,
//当他自己找this.name的时候当然找所在的地方的上一级
//,当他运行时还是会找到自己上面的this
Maid.whatIsYourName() //陆因为调用他自己的,他属于是Maid的所以他上一级就是this.name就为陆
}
School();
</script>
- 两个指向的时候 一个指向school 一个指向Maid
- 因为function的this 会指向上一个堆栈里的对象
- 同样的 如果你是在嵌套function的话,
- function里面又有function,那么就得看你代码里面 是怎么样子的一个进堆栈的方式
- 然后this 向上查找
- 所以 this并不总是指全局,指是指向function里面的上一个对象
然后 呢 知道了这个再来看 箭头函数就简单了
-
箭头函数是es6提出的,也就是2015年提出的
-
也就是 2015年的时候 设计人员觉得早期的function设计有点功能不够,再加上来的
-
所以 实际上箭头函数只是个语法糖
-
有很多工具可以将es6转成es5,也就是babel等这些工具
-
就是将箭头函数 await promise这些转成老版本的表达
-
箭头函数实际上的本质 只不过是
-
在定义箭头函数的时候,先存一下最外面的this
-
然后将箭头函数里面的this 替换成外面的这个p
-
因为箭头函数是新增的补丁,所以不大可能改进 function的内核,只是加个语法替换的补丁而已
-
这就是个历史原因了,function的内核设定已经是this指向上一级了,要改起来就很头疼了
-
加个文本替换,就出现了箭头函数
-
而且 多出了一个p, p不是个放在function的指针
-
p是在定义的时候就写死的
-
这个时候箭头函数里面的this 在运行的时候会替换成p
-
箭头函数的代码本体也是不跟obj放一起的
-
也是外放的,但是 它的所有this都会重定向到p
-
然后p再指向定义的时候的this
-
这个p不一定指向obj 也可以指向别的,因为我们上面的示例是定义在obj的,所以指向obj
-
(你看)
const Maid = {
name: "陆",
/*相当于
var p = this 这个this指出去,那就是全局啦
*/
whereAreYou: () => {
// this
console.log("我在${this.name}")
},
whatIsYourName: function() {
//这this 指的是上一级对象
console.log('我是${this.name}');
}
}
** 以上整理来自我与师傅的聊天对话**
总结: 普通函数function的this跟谁调用它有关,比如是js调用呀,那它的指向就是js规定的windows,(1.那是不是所有的普通函数都是js调用的,那永远指的都是windows?)-之后探讨。而箭头函数的this指的是上一级的this,假如是上级this指的是vue,那它的this就是vue.
-
探讨结果出来了
-
1.凡是在函数内部调用的函数 this 都指向window
-
2、在事件中一般情况下 this 的指向都指向当前对象
-
3、在对象的函 数中一般情况 this 的指向都指向当前对象
-
4、计时器 this 的指向都指向window
下面这篇文章讲的很彻底普通函数的指向问题,忘记的时候就点击查看。
参考文章:知乎
彻底总结:箭头函数的this指向,指的永远是上一级的this,上一级指的是哪,它就是哪,普通函数的this指向总结:
1.凡是在函数内部调用的函数 this 都指向window
2、在事件中一般情况下 this 的指向都指向当前对象
3、在对象的函 数中一般情况 this 的指向都指向当前对象
4、计时器 this 的指向都指向window
参考文章:点击跳转
***总结:
- 构造函数和普通函数的指向问题。看下面代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>普通函数和构造函数this指向问题</title>
</head>
<body>
<button>点击</button>
<script type='text/javascript'>
// this 指向问题 一般情况下this的最终指向是那个调用它的对象
// 1.全局作用域或者普通函数中this指向全局对象 window
//(注意定时器里面的this指向windows
//因为它其实是属于windows下面的函数或者是方法,是window在调用它)
console.log(this); // 全局windows
function fn() {
console.log(this); //window
}
//windows调用
window.fn();
window.setTimeout(function() {
console.log(this);
//window
}, 1000)
//2.方法调用中谁调用就指向谁
var o = {
sayHiy: function() {
console.log(this); //只向 o对象
}
}
o.sayHiy();
var btn = document.querySelector('button')
btn.onclick = function() {
console.log(this); //this指向的它的调用者也就是 btn
//this 指向的是btn这个按钮对象
}
btn.addEventListener('click', function() {
console.log(this); //this 指向的是btn 这个按钮对象 因为是btn这个按钮对象调用了它
})
//3.构造函数中this指向构造函数实例
function Fun() {
console.log(this); //this 指向的是 fun 实例对象
}
var fun = new Fun()
</script>
</body>
</html>