一、箭头函数arrow function
箭头函数(Arrow Function)是ES6(ECMAScript 2015)引入的一种新的函数书写方式,并且它比函数表达式要更加简洁
-
箭头函数更加简洁,拥有不同的
this
上下文绑定行为,无arguments属性 -
箭头函数不能作为构造函数来使用(不能和new一起来使用,会抛出错误)
1.1. 什么是箭头函数
-
箭头函数有另一个名称,叫做
"胖箭头"
,说的是=>
这个符号,其实挺形象的
图9-1 箭头函数的箭头
-
箭头函数如何编写?
-
()
:函数的参数编写位置 -
{}
:函数的执行体编写位置
-
-
而"胖箭头"就是将
()和{}
结合起来的桥梁()=>{ //执行体 }
-
在我们简单的相加案例中。
a,b
是参数,a+b是执行体。通过编写位置可以简单的区分出来 -
而执行体为什么没有用
{}
包裹起来,为什么没有return。都是我们本章节要详细讲解的 -
而答案其实隐藏在之前的章节之中,如果大家有注意到的话,估计已经可以串联起来了
-
在这里我们先来见一下箭头函数的基础模板,你一定不陌生,在第六章实现五种高阶函数的时候,我们是超前使用了一下箭头函数
-
//箭头函数写法
const add = (a, b) => a + b;
//普通写法
function add(a, b) {
return a + b;
}
console.log(add(5, 3)); // 输出: 8
1.2. 箭头函数使用解析
表9-1 箭头函数结构分解
要素 | 描述 | 作用 |
---|---|---|
() | 参数 | 定义函数的输入,即函数的参数。当箭头函数没有参数或有多个参数时使用圆括号,对于单个参数,圆括号可以省略 |
=> | 箭头 | 标示这是一个箭头函数,它连接参数和函数体,明确了函数的定义。箭头的左边是参数部分,右边是函数体 |
{} | 函数执行体 | 包含了函数的执行语句。当函数体只包含一条语句且返回一个值时,大括号和return 语句都可以省略。如果函数体有多条语句,或者需要执行更复杂的逻辑,就需要使用大括号,并在需要返回值时使用return 语句 |
-
我们以forEach这一高阶函数为例
-
在我们普遍的写法中,是直接传递回调函数进行作为第一个参数的,通常这类函数都不需要名字,也基本上不会复用,需要的只是产生的结果(处理后的数据)
-
但完整的写法,我们这个回调函数确实就是一个函数,我们完全可以在外面写好函数之后,传递进去。不过既然要传递,自然就不能使用匿名函数了,这
回调函数
得有一个名称,让我们传递到forEach高阶函数中
-
//方式1:
var nums = [10,20,30,40]
nums.forEach((value,index,array)=>{
console.log(value,index,array)
})
//方式2(完整写法):
var foo = (value,index,array)=>{
console.log(value,index,array)
}
var nums = [10,20,30,40]
nums.forEach(foo)
-
但我们基本上不使用完整的写法,因为在处理数据的时候,逻辑基本上不太可能完全一样
-
而且完整方式的写法,在处理数据和处理位置的逻辑感上是有点割裂的
-
如果迭代逻辑仅用一次或是非常简单,使用完整方式会显得冗余。但如果逻辑复杂的话,我们就会进行单独抽取逻辑进行封装
-
所以常用方式1写法,也被称为内联方式,迭代逻辑紧密地绑定于使用场景,代码的直观性有很大的提升,这样的代码更容易理解,因为它没有
foo变量名
这样的额外间接层
-
1.2.1 箭头函数常见简写方法
-
简写1:省略参数的小括号
-
条件:当箭头函数只有一个参数时,可以省略参数周围的小括号
-
-
简写2:省略函数体的大括号和
return
关键字-
条件:当函数体只包含一条语句,并且是返回值的表达式时,可以省略大括号。在这种情况下,该语句的结果会自动成为函数的返回值
-
这种写法被称为
隐式返回
,因为不需要我们主动return -
但如果函数体需要执行多条语句,必须使用大括号,并且使用
return
语句来返回结果,除非函数不需要返回值
-
//简写1:如果参数只有一个,小括号可以省略
//简写前:
nums.forEach((item)=>{
console.log(item)
})
//简写后:
nums.forEach(item=>{
console.log(item)
})
//简写2:如果执行体只有一个,大括号可以省略
nums.forEach(item => console.log(item))
//强调:并且它会默认将这行代码的执行结果作为返回值,所以我们不需要return
var newNums = nums.filter(item => item % 2 === 0)//item % 2 === 0的结果会默认返回
console.log(newNums)
//一般情况下我们带大括号的是需要手动return返回的,就像这样
var newNums = nums.filter(item => {
return item % 2 === 0//这种有大括号的情况下,如果我们不return的话,会返回[]
})
-
在这里需要给大家说明一下,在简洁性和可读性方面,后者会比前者更加重要。不要刻意去追求极简的高端写法,这会让代码变得难以理解,需要在这其中找到一个平衡的支点
-
我们来实现一个简单的需求:
-
从一个包含多个对象的数组中筛选出满足特定条件的对象,并从这些对象中提取特定的属性形成一个新数组
-
具象化需求:我们有一个代表书籍的数组,需要筛选出所有评分高于4的书籍,并从中提取书名
-
const books = [
{ title: "Book A", rating: 4.5 },
{ title: "Book B", rating: 3.9 },
{ title: "Book C", rating: 4.7 }
];
//简洁写法
const titles = books.filter(book => book.rating > 4).map(book => book.title);
//普通写法
var highRatingBooks = [];//筛选符合的书籍
for (var i = 0; i < books.length; i++) {
if (books[i].rating > 4) {
highRatingBooks.push(books[i]);
}
}
var titles2 = [];//提取出符合书籍的标题
for (var i = 0; i < highRatingBooks.length; i++) {
titles.push(highRatingBooks[i].title);
}
console.log(titles); // ["Book A", "Book C"]
console.log(titles2); // ["Book A", "Book C"]
-
这种案例实在太经典了,普通的写法是一开始学习时候的写法,而展示的
简洁写法
除了使用箭头函数之外,还使用了一种叫做链式调用
的方式来提高简洁性。从而实现了一行代码顶十行的效果-
我们的技术最终都会走向成熟,在一开始的时候没有必要去追求过于简写的方式
-
等到自身水平达到临界点之后,更好的写法自然会水到渠成的掌握
-
-
在React中,到处都充斥着这种
简洁写法
,并且组合方式极度自由,对我们的JS高级内容的要求就很高了-
这就是为什么React的学习难度在一定程度上会大于Vue的原因
-
但不管哪种方式,都是我们的必经之路,因为我们每一个人都是从普通写法经历过来的,不要对普通写法抱有偏见
-
//简写3:如果一个箭头函数,只有一行代码,并且返回一个对象,这个时候如何编写简写
var bar = ()=>{
return {
name:"小余",
age:20
}
}
//如果你按照上面的简写思路的话,那应该是
var bar = ()=> {name:"小余",age:18}//但这种写法其实是错误的,因为这里会发生混乱,这个大括号到底是判定为执行体还是对象呢?JS引擎会发生错乱
//正确的简写方式
var bar = ()=> ({name:"coderwhy",age:18})
//使用小括号将对象包裹起来,这个是将对象当作一整个整体
-
在简写3中,是很多初学者会犯错的地方,JS引擎会将大括号
{}
解释为函数执行体的开始和结束,而里面的name: "小余", age: 18
被视为标签语法(label),并非对象字面量。这样不会创建和返回一个对象,而是没有返回值(默认返回undefined
),因为这里的语法并不形成有效的返回语句//更通俗易懂的写法 var bar = ()=> { return {name:"小余",age:18} } console.log(bar());
图9-2 简写3-通俗易懂的写法及结果-
而有些人会喜欢利用这种简写上容易产生语法冲突的方式来进行炫技,而这也是不提倡的事情
-
而小括号的作用我们之前有说过,将其内容视为一个整体,这样就不会产生冲突了
-
说到底其实不应该在会产生冲突的地方去考验语法,因为内容在箭头函数语法之中,箭头函数的语法本身优先度是更高的,
{}
会被箭头函数视为一个整体,而非对象被视为一个整体,小括号视为一个整体的方式就像数学表达式中所产生的效果是一样的
-
1.2.2 箭头函数的this获取
-
箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this
-
在社区之中,有两种说法:
-
箭头函数没有this
-
箭头函数this由外层作用域决定
-
-
其实两种说法都是对的,但如果是第一次了解,我们可能会有点迷惑,这两者不是冲突的吗?
-
箭头函数不创建自己的
this
上下文,因此它没有自己的this
值。箭头函数内部的this
值由外围最近一层非箭头函数的执行上下文决定 -
所以当大家说“箭头函数没有
this
”时,他们的意思是箭头函数不创建自己的this
上下文,this
是继承自外围作用域 -
而说箭头函数有this的情况,则是说继承自外围作用域的this。这个this不是箭头函数自己的,而是继承来的
-
-
所以准确的说法是:箭头函数本身没有this,但可以继承外围作用域的this
-
而外围作用域(也称为外部作用域或父作用域)指的是包围或包含当前函数的那个作用域
-
var name = "小余"
var foo = ()=>{
console.log(this);
}
foo()//window
var obj = {foo:foo}
obj.foo()//window
foo.call("这是call调用的")//window
-
为什么上面3种不同方式的调用都是window?
-
首先那是因为我们foo的上层作用域是全局的,全局的可不就是window
-
然后就是我们的
隐式绑定obj
和显式绑定call
为什么没有改变成功this的指向呢?那是因为箭头函数的原因,箭头函数不会绑定this属性,而我们的foo函数恰巧使用了箭头函数,而foo的外围作用域也就是父级作用域,是全局,造成了所有的绑定效果都是指向window,这是很有用的一个特点
-
-
接下来让我们使用
隐式调用
看看有无箭头函数的this对比
//对比前,没有使用箭头函数
var name = "小余"
function foo(){
console.log(this);
}
// foo()
var obj = {name:"你已经被小余绑定到obj上啦",foo:foo}
obj.foo()// Node打印效果:{ name: '你已经被小余绑定到obj上啦', foo: [Function: foo] }
//对比后,使用箭头函数
var name = "小余"
var foo = ()=>{
console.log(this);
}
var obj = {name:"你已经被小余绑定到obj上啦",foo:foo}
obj.foo()//window
-
非常直观的效果,因为隐式绑定是我们使用最高频的情况,也是最容易出错的地方
-
因为显式调用是我们主观要去进行的行为,不容易忽略,我们可以很快的发现问题。大多数出错的往往下意识使用到隐式调用导致忽略this指向问题
-
所以使用this之前往往需要考虑到使用场景
-
1.2.3. 箭头函数应用场景
-
大多数的应用场景,其实我们是已经使用过了
-
链式调用最常见的场景就是发起网络请求了,之前的案例中也缺少这一部分,我们就拿这个举例
-
柯里化我们留在下一章节中进行讲解
-
表9-2 箭头函数使用场景总结
使用场景 | 描述 | 例子 |
---|---|---|
回调函数 | 在事件监听和异步处理中使用,继承外部this 上下文。 | setTimeout(() => console.log(this), 1000) |
数组操作 | 与map 、filter 、reduce 等方法配合使用。 | nums.map(n => n * 2) |
简洁表达 | 一行代码完成函数定义。 | const square = x => x * x |
链式调用 | 适用于Promise链和其他流式API。 | fetch(url).then(res => res.json()) |
柯里化和部分应用 | 简化函数柯里化和部分应用的实现。 | const add = x => y => x + y |
1.2.3.1. 网络请求
-
为什么说网络请求是
链式调用
最常见的场景呢?-
因为前端请求数据是通过
接口
来进行的,接口是一个URL地址,由后端程序员给我们的 -
我们在一开始的章节中就有讲过URL传递进浏览器需要哪些步骤,而一旦通过URL地址访问,这些步骤势必是需要时间的,接口也是一样的,从服务器传输下来的时间
-
从请求数据到接收数据之间有一个等待过程,这期间我们的代码应该要继续往下执行,而不是卡在这里进行等待。而这就是
异步
,更多详细的内容,我们在后面篇章中会和Promise进行结合学习
-
-
由于还未讲到相关的知识内容,我们用定时器来模拟场景
-
我们模拟2秒后数据
result
会返回给我们,此时我们需要接收数据 -
我们的
data变量
就是用来接收数据的,这就为难了,我接收数据的话,如果用this的话,普通的函数是无法指向父级作用域的data变量的,指向的是window -
所以在没有箭头函数的时候,我们会在函数内部使用
var _this = this
,从而强行绑定父级作用域
-
//无箭头函数时候
var obj = {
data:[],
getData:function(){
//在没有箭头函数的时候,大家通常是这么解决问题的
var _this = this//这里的this就是obj对象了,getData的上一层可不就是obj
setTimeout(function(){//没有使用箭头函数,会出现问题,所以要加上var _this = this,然后使用_this
var result = ["小余",'coderwhy','JS高级']
_this.data = result//_this是外层的变量,这里就形成了一个闭包
console.log(this)
},2000)
}
}
obj.getData()//没有使用箭头函数或者没有声明一个变量来接收getData里面的this的时候,为什么是window,那是因为foo函数绑定到obj上面啦,obj的上层就是全局window了,这是隐式绑定
-
var _this = this
,使用_this和直接this的区别在哪?不都是一样的吗?-
在
setTimeout
等异步函数内部直接使用this
,会遇到this
指向全局对象的问题,因为这些函数不是由obj
直接调用的,这一点,我们在上一章节中的this分析中,有专门提出 -
而声明
var _this = this;
是以前常见的模式,用于在闭包中保持对外部this
的引用。在这种模式下,_this
变量保存了getData
方法调用时的this
值(即obj
对象)
-
-
这常见的模式,核心理念在于用闭包保持对外部this的引用
-
首先我们需要知道,存放在堆内存当中的有数组和对象,而我们此时刚好是一个对象。在全局VO对象当中,只能找到obj,而不能找到data属性。因为data属性是属于obj内部的东西
-
obj会进行变量提升,一开始为默认undefined,之后到赋值的位置,会转化为指向堆内存的地址,如果我们想要找到data就必须沿着obj的这个堆内存地址去找data属性,也就是obj.data。从内存角度相当于0xa00.data。这里的0xa00就相当于一个内存地址
-
所以,在window当中,我们是绝对找不到data属性的,而定时器的内部this又会指向于window。我们就必须解决这个问题,在obj对象当中,getData接收的是一个函数。那好,我在这个函数当中,直接
var _this = this
先绑定this了,因为既然我如果想要调用getData方法,基本上都是obj.getData()
进行调用,那这不就是我们所学的隐式绑定吗? -
挺好的,这样我们的this就直接绑到obj对象上面去了,想拿data属性就简单了。而在定时器中,我们调用
_this
这个变量来做出赋值的操作,形成了闭包,保持了引用,就不会导致_this
被JS引擎释放掉了 -
而这就是
var _this = this
操作的核心目的
-
图9-3 var _this = this操作内存图
-
说这么多其实也挺费劲的,但好在有箭头函数,直接就不受this的影响,this直接就是父级作用域,直接省下了我们一堆分析的功夫
-
但不代表this的分析我们不需要掌握,因为箭头函数并不是完全替代this的使用场景,而是对于普遍需求的一种补充和优化
-
而且很多老代码当中还是存在这些情况,如果不理解的话,在进行优化修改代码的时候就会投鼠忌器,没用到也不敢删不敢修改
-
使用箭头函数情况去使用this直接理解为指向上层作用域就好了,理解难度一下就下降了很多
-
//有箭头函数的时候
var obj = {
data:[],
getData:function(){
setTimeout(()=>{
var result = ["小余",'coderwhy','JS高级']
this.data = result//直接使用this
console.log(this)//通过直接打印this进行检测
},2000)
}
}
obj.getData()
-
最后,让我们来看下正式的网络请求中,代码是怎么样的吧,不需要看里面的代码详情,但从格式上,你一定可以看出共通之处
图9-4 正式网络请求存储(this指向)
二、this面试题
-
在学习了箭头函数之后,我们补全了关于this的最后一块缺失的拼图,那么就需要来验验货了,看所学内容是否牢固扎实
-
在正式开始之前,我还有一点内容想说,那就是做面试题是为了什么
-
首先,对于简单的内容,面试题能起到复习且快速补充实力的作用
-
但对于真正较难且需要深入理解的内容来说,面试题只能起到检测的作用,并不能让我们真正做到从内向外的提升,因为缺乏体系的学习很难真正的理解,盲人摸象所摸到的内容未必是虚假的,学到的是真的,但理解却是片面的,但对于整体的理解还是一片的迷雾,学着学着可能就偏离轨道,然后就迷茫了
-
所以背面试题是最差的选择,最好是理解后用自己的话说出来,否则背着背着就焦虑了,背不下来,然后在看一些面试题文章,就更难受了。
-
如果熟练使用GPT4来辅助我们进行学习面试题的话,是可以起到理解全面的作用的,但前提是提问方式和准度需要高一些,我更建议自己总结一遍理解,然后让GPT4进行针对性的指出错误点进行不断的优化。这种自学的方式省钱且高效,就是对自律和自觉性有较大的要求。毕竟自学在渐入佳境之前是需要花费很多时间去试错寻找适合自己的方式的
-
-
如果觉得不太自信的话,或者希望有人能指导自己编写简历,在这里也推荐一下coderwhy老师最新推出的面试课程,质量肯定是值得保证的,会进行实际操作讲解,包含了就业篇、加薪篇和架构篇,价格也不贵,在这里放出就业篇的部分让大家感受一下,有需求的可以入手
-
学完JS高级系列的文章和视频,有关于JS的面试题一定难不倒我们,图中的JS面试题,通过这几个篇章的学习后,已经能解决一大半了,等我们学习完所有内容,我们所能解决的JS难点也一定会远远超出下面这JS部分的二十多题的
-
时间不紧迫的可以学习本系列的文章和视频,免费的,一步步打牢基础。紧迫的可以通过面试系列视频来快速提升自己的面试通过率,然后在回头来打牢基础。没有哪种方式会绝对的好,适合自己的才是最好的
-
图9-5 基础篇面试题大纲
2.1. 面试题1
这道面试题非常简单,无非就是绕一下,希望把面试者绕晕:
//无答案解析版本
var name = "window"
var person = {
name:"person",
sayName:function(){
console.log(this.name);//这里的答案是谁
}
};
function sayName(){
var sss = person.sayName
sss();//调用打印出来的是什么
person.sayName();//?
(person.sayName)();//?
(b = person.sayName)();//?
}
sayName()
-
其实考验的就是三种不同的调用方式:隐式调用、独立函数调用、间接函数调用
-
独立函数调用 sss()
:,sss接收的内容就是person.sayName的内存地址,但这是没有任何前缀的,不属于函数方法,是默认绑定
的一种,而默认绑定的this自然就指向于window了,只要不被其他优先度更高的绑定规则所覆盖,就不需要考虑他的位置 -
间接函数引用 (b = person.sayName)()
:这前者的小括号就不是迷惑人眼球的内容了,而是确实产生不同的效果,b = person.sayName
如果没通过小括号划分为一个整体,b接收到的就是person.sayName
隐式调用的结果,this就该指向于person了。但前者既然划分为一个整体,那就需要先考虑赋值操作,最终执行的就该是b()
,因为b是最终的结果,而b已经拿到person.sayName的内存地址了。这跟独立函数调用直接就是一个意思了,也是默认绑定的一种,直接指向于window -
隐式函数调用
在这里体现为两种方式,一种明显,另一种则具备迷惑性
-
person.sayName()
:一看this就应该指向于person对象,所以this.name妥妥的是person -
(person.sayName)()
:但我要是在调用之前,加上括号,这要如何区分?首先这两个小括号有不同的作用,前者是划分为一个整体,后者是调用。但我们需要知道person.sayName本身就是堆内存地址,并不像分散的内容需要划分为一个整体,所以这里的括号加了跟没加一样,属于没必要的操作,就是为了迷惑你的,是属于降低可读性的操作,实际中不建议使用。答案还是person
-
//答案解析版本
var name = "小余window"
var person = {
name:"person",
sayName:function(){
console.log(this.name);
}
};
function sayName(){
var sss = person.sayName
sss();//this.name是小余window ,独立函数调用,所以这里的this指向最外层的window
person.sayName();//隐式调用,this指向person,控制台打印的this.name是person
(person.sayName)();//person,隐式调用
(b = person.sayName)();//间接函数引用,是独立的函数调用,所以是小余window,(b = person.sayName)是一个整体
}
sayName()
2.2. 面试题2
-
该面试题着重考验的是显示调用的优先级水平以及this在箭头函数下的情况
-
算是本章节箭头函数和this结合最相关的一道面试题
-
在这里需注意的一个点就是,我一旦使用()进行调用了,往前的内容就会被执行结果所替代。例如
foo.foo1()
,然后foo1会return一个函数,则foo.foo1()()就等于foo.foo1()
返回的结果再执行调用一次 -
因为我们
()()
连续调用了两次,第一次调用返回了一个函数,第二次调用就调用这返回的函数
-
var name = 'window'
//person1是字面量对象
var person1 = {//定义对象的时候是不会产生作用域的,所以对象里面的上层在对象外面
name: 'person1',
foo1: function () {
console.log(this.name)
},//普通函数
foo2: () => console.log(this.name),//箭头函数
foo3: function () {
return function () {
console.log(this.name)
}
},//函数套函数,返回普通函数
foo4: function () {
return () => {
console.log(this.name)
}
}//函数套函数,返回箭头函数
}
var person2 = { name: 'person2' }
// person1.foo1(); // person1(隐式绑定)
// person1.foo1.call(person2); // person2(显示绑定优先级大于隐式绑定)
// person1.foo2(); // window(不绑定作用域,上层作用域是全局)
// person1.foo2.call(person2); // window
//这里的person1.foo3()的调用下拿到结果在()继续调用,这种属于独立调用 因为foo3()调用后是返回一个全新的函数,相当于一个匿名函数的独立调用
// person1.foo3()(); // window(独立函数调用)
// person1.foo3.call(person2)(); // window(独立函数调用)
// person1.foo3().call(person2); // person2(最终调用返回函数式, 使用的是显示绑定)
// person1.foo4()(); // person1(箭头函数不绑定this, 上层作用域this是person1)
// person1.foo4.call(person2)(); // person2(上层作用域被显示的绑定了一个person2)
// person1.foo4().call(person2); // person1(上层找到person1)
2.3. 面试题3
-
通过new创建对象的方式是this优先度最高的,但我们还没学到面向对象的部分,所以还是有点超纲难度的
-
可以先自行体验一下
-
var person1 = new Person('person1')
var person2 = new Person('person2')
//创建出来的2个新对象:
this = {name:"person1",foo1:function{}}
this = {name:"person2",foo1:function{}}
-
连续new了两次,代表构造函数会被连续调用两次(什么是构造函数,我们后续章节会讲解)
-
每次new的时候都会创建一个新的对象,这里new了两次表示创建了两个新的对象
var name = 'window'
function Person (name) {//作为构造函数,一般情况下,我们都开头字母大写
this.name = name
this.foo1 = function () {
console.log(this.name)
},
this.foo2 = () => console.log(this.name),
this.foo3 = function () {
return function () {
console.log(this.name)
}
},
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.foo1() // person1
person1.foo1.call(person2) // person2(显示高于隐式绑定)
person1.foo2() // person1 (上层作用域中的this是person1)
person1.foo2.call(person2) // person1 (上层作用域中的this是person1)
person1.foo3()() // window(独立函数调用)
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2
person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1
var obj = {
name: "obj",
foo: function() {
}
}
2.4. 面试题4
通常我们会对什么时候调用感到疑惑,例如下面这两个
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj
-
他们的区别从foo2开始发生不同,foo2.xxx表示到了foo2还没调用,而是继续深入到里面
-
foo2()则是调用了,然后foo2属性对应的将会生效替代foo2()部分,变为foo2().call(person2)
-
在下面的表达式中,foo2()属性调用则是return了一个箭头函数,既然return了,那就跳出外面一层function了,且箭头函数是不受call改变this的,this的指向当然就是obj咯(return出来的函数的上一层或者说父级作用域就是obj函数),所以this.name自然就是obj对象里面的name:obj
var name = 'window'
function Person (name) {
this.name = name
this.obj = {//对象里面封装对象
name: 'obj',
foo1: function () {
return function () {//普通返回
console.log(this.name)
}
},
foo2: function () {
return () => {//箭头函数返回
console.log(this.name)
}
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2
person1.obj.foo2()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj
//
// 上层作用域的理解
// var obj = {
// name: "obj",
// foo: function() {
// // 上层作用域是全局
// }
// }
// function Student() {
// this.foo = function() {
// }
// }