箭頭函數(shù)和普通函數(shù)的區(qū)別:
首先補(bǔ)一下 this 是什么?
this:
this 是和執(zhí)行上下文綁定的,也就是說每個(gè)執(zhí)行上下文中都有一個(gè) this。

上圖中 outer 是在變量環(huán)境里面的,為了方便看
我們都知道 js 在編譯階段 創(chuàng)建執(zhí)行上下文在每個(gè)執(zhí)行上下文的變量環(huán)境中,都包含了一個(gè)外部引用,用來指向外部的執(zhí)行上下文,我們把這個(gè)外部引用稱為 outer
全局執(zhí)行上下文中的 this:
全局執(zhí)行上下文中的 this 是指向 window 對(duì)象的。這也是 this 和作用域鏈的唯一交點(diǎn),作用域鏈的最底端包含了 window 對(duì)象,全局執(zhí)行上下文中的 this 也是指向 window 對(duì)象
函數(shù)中的this:
function foo(){
console.log(this)
}
foo()
// 也是指向window
那能不能設(shè)置執(zhí)行上下文中的 this 來指向其他對(duì)象呢? 肯定是可以的
1、通過函數(shù)的 call 方法設(shè)置:
let bar = {
myName : "極客邦",
test1 : 1
}
function foo(){
this.myName = "極客時(shí)間"
}
foo.call(bar)
console.log(bar) // {myName: "極客時(shí)間", test1: 1}
console.log(myName) // myName is not defined
你就能發(fā)現(xiàn) foo 函數(shù)內(nèi)部的 this 已經(jīng)指向了 bar 對(duì)象,
因?yàn)橥ㄟ^打印 bar 對(duì)象,可以看出 bar 的 myName 屬性已經(jīng)由“極客邦”變?yōu)椤皹O客時(shí)間”了,
同時(shí)在全局執(zhí)行上下文中打印 myName, myName is not defined
2、通過對(duì)象調(diào)用方法設(shè)置:
要改變函數(shù)執(zhí)行上下文中的 this 指向,除了通過函數(shù)的 call 方法來實(shí)現(xiàn)外,還可以通過對(duì)象調(diào)用的方式
var myObj = {
name : "極客時(shí)間",
showThis: function(){
console.log(this)
}
}
myObj.showThis() // {name: "極客時(shí)間", showThis: ?}
使用對(duì)象來調(diào)用其內(nèi)部的一個(gè)方法,該方法的 this 是指向?qū)ο蟊旧淼摹?/p>
var myObj = {
name : "極客時(shí)間",
showThis: function(){
this.name = "極客邦"
console.log(this)
}
}
var foo = myObj.showThis
foo()
//你會(huì)發(fā)現(xiàn) this 又指向了全局 window 對(duì)象。
- 在全局環(huán)境中調(diào)用一個(gè)函數(shù),函數(shù)內(nèi)部的 this 指向的是全局變量 window。
- 通過一個(gè)對(duì)象來調(diào)用其內(nèi)部的一個(gè)方法,該方法的執(zhí)行上下文中的 this 指向?qū)ο蟊旧怼?/strong>
3. 通過構(gòu)造函數(shù)中設(shè)置:
function CreateObj(){
this.name = "極客時(shí)間"
console.log(this) // CreateObj {name: "極客時(shí)間"}
}
var myObj = new CreateObj()
new CreateObj() 過程:
1、首先創(chuàng)建了一個(gè)空對(duì)象 tempObj;
2、接著調(diào)用 CreateObj.call 方法,并將 tempObj 作為 call 方法的參數(shù),這樣當(dāng) CreateObj 的執(zhí)行上下文創(chuàng)建時(shí),它的 this 就指向了 tempObj 對(duì)象;
3、然后執(zhí)行 CreateObj 函數(shù),此時(shí)的 CreateObj 函數(shù)執(zhí)行上下文中的 this 指向了 tempObj 對(duì)象;
4、最后返回 tempObj 對(duì)象。
嵌套函數(shù)中的 this 不會(huì)從外層函數(shù)中繼承
var myObj = {
name : "極客時(shí)間",
showThis: function(){
console.log(this)
function bar(){console.log(this)}
bar()
}
}
myObj.showThis()
//函數(shù) bar 中的 this 指向的是全局 window 對(duì)象,
// 而函數(shù) showThis 中的 this 指向的是 myObj 對(duì)象
那怎么實(shí)現(xiàn) bar 的this指向外層 myObj 呢?
var myObj = {
name : "極客時(shí)間",
showThis: function(){
console.log(this)
var self = this
function bar(){
self.name = "極客邦"
}
bar()
}
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)
本質(zhì)是把 this 體系轉(zhuǎn)換為了作用域的體系 。也可以使用 ES6 中的箭頭函數(shù)來解決這個(gè)問題
var myObj = {
name : "極客時(shí)間",
showThis: function(){
console.log(this) // {name: "極客時(shí)間", showThis: ?}
var bar = ()=>{
this.name = "極客邦"
console.log(this) // {name: "極客邦", showThis: ?}
}
bar()
}
}
myObj.showThis()
console.log(myObj.name) // 極客邦
console.log(window.name) // ''
因?yàn)?ES6 中的箭頭函數(shù)并不會(huì)創(chuàng)建其自身的執(zhí)行上下文,所以箭頭函數(shù)中的 this 取決于它的外部函數(shù)
- 當(dāng)函數(shù)作為對(duì)象的方法調(diào)用時(shí),函數(shù)中的 this 就是該對(duì)象;
- 當(dāng)函數(shù)被正常調(diào)用時(shí),在嚴(yán)格模式下,this 值是 undefined,非嚴(yán)格模式下 this 指向的是全局對(duì)象 window;
- 嵌套函數(shù)中的 this 不會(huì)繼承外層函數(shù)的 this 值
下面說下 普通函數(shù)和箭頭函數(shù)的區(qū)別:
箭頭函數(shù)和普通函數(shù)的this:
1、this 指向不同:
- 普通函數(shù)this 指向 為方法調(diào)用的對(duì)象,可以通過bind,call,apply,改變this指向
- 箭頭函數(shù)比函數(shù)表達(dá)式更簡(jiǎn)潔,箭頭函數(shù)不會(huì)創(chuàng)建自己的this,它只會(huì)從自己的作用域鏈的上一層繼承this。bind,call,apply只能調(diào)用傳遞參數(shù),不可修改this指向
var obj = {
a: 10,
b: () => {
console.log(this.a); // undefined
console.log(this); // Window {postMessage: ?, blur: ?, focus: ?, close: ?, frames: Window, …}
},
c: function() {
console.log(this.a); // 10
console.log(this); // {a: 10, b: ?, c: ?}
}
}
obj.b();
obj.c();
箭頭函數(shù)不綁定this,會(huì)捕獲其所在的上下文的this值,作為自己的this值。
任何方法都改變不了其指向
var obj = {
a: 10,
b: function(){
console.log(this.a); //10
},
c: function() {
return ()=>{
console.log(this.a); //10
}
}
}
obj.b();
obj.c()();
箭頭函數(shù)通過 call() 或 apply() 方法調(diào)用一個(gè)函數(shù)時(shí),只傳入了一個(gè)參數(shù),對(duì) this 并沒有影響。
補(bǔ)充:call,aplly,bind:
它們?cè)诠δ苌鲜菦]有區(qū)別的,都是改變this的指向,它們的區(qū)別主要是在于方法的實(shí)現(xiàn)形式和參數(shù)傳遞上的不同。call和apply方法都是在調(diào)用之后立即執(zhí)行的。而bind調(diào)用之后是一個(gè)函數(shù),需要再調(diào)用一次才行,
①:函數(shù).call(對(duì)象,arg1,arg2....)
②:函數(shù).apply(對(duì)象,[arg1,arg2,...])
③:var ss=函數(shù).bind(對(duì)象,arg1,arg2,....)
let obj2 = {
a: 10,
b: function(n) {
let f = (n) => n + this.a;
return f(n);
},
c: function(n) {
let f = (n) => n + this.a;
let m = {
a: 20
};
return f.call(m,n);
}
};
console.log(obj2.b(1)); // 11
console.log(obj2.c(1)); // 11
2、箭頭函數(shù)沒有原型,
var a = ()=>{
return 1;
}
function b(){
return 2;
}
console.log(a.prototype); // undefined
console.log(b.prototype); // {constructor: ?}
3、箭頭函數(shù)不能綁定arguments,取而代之用rest參數(shù)...解決
function A(a){
console.log(arguments);
}
A(1,2,3,4,5,8);
// [1, 2, 3, 4, 5, 8, callee: ?, Symbol(Symbol.iterator): ?]
let C = (...c) => {
console.log(c);
}
C(3,82,32,11323);
// [3, 82, 32, 11323]
4、箭頭函數(shù)是匿名函數(shù),不能作為構(gòu)造函數(shù),不能使用new
無法實(shí)例化的原因:
沒有自己的this,無法調(diào)用call,apply
沒有prototype屬性,而new命令執(zhí)行的時(shí)候需要將構(gòu)造函數(shù)的prototype賦值給新的對(duì)象的_proto
5、箭頭函數(shù)不可以使用 yield 命令,因此箭頭函數(shù)不能用作 Generator 函數(shù)。
6、 函數(shù)體內(nèi)的this對(duì)象(繼承的),就是定義時(shí)所在的對(duì)象,而不是使用時(shí)所在的對(duì)象。
如何實(shí)現(xiàn) call 和apply?
1、改變this指向:可以將目標(biāo)函數(shù)作為這個(gè)對(duì)象的屬性
2、利用arguments類數(shù)組對(duì)象實(shí)現(xiàn)參數(shù)不定長(zhǎng)
3、不能增加對(duì)象的屬性,所以在結(jié)尾需要delete
var doThu = function(a, b) {
console.log(this)
console.log(this.name)
console.log([a, b])
}
var stu = {
name: 'xiaoming',
doThu: doThu,
}
stu.doThu(1, 2) // stu對(duì)象 xiaoming [1, 2]
doThu.call(stu, 1, 2) // stu對(duì)象 xiaoming [1, 2]
call的作用就與此相當(dāng),只不過call為stu添加了doThu方法后,執(zhí)行了doThu,然后再將doThu這個(gè)方法從stu中刪除。
//在函數(shù)原型上增加call1方法
Function.prototype.call1 = function(context, ...rest) {
let newContext = context || window
newContext.fn = this // 將調(diào)用call函數(shù)的對(duì)象添加到context的屬性中
let result = newContext.fn(...rest) // // 執(zhí)行該屬性
delete newContext.fn // 刪除該屬性
return result
}
//在函數(shù)原型上增加apply1方法
Function.prototype.apply = function(thisArg, args) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
thisArg = thisArg || window
thisArg.fn = this
let result
if(args) {
result = thisArg.fn(...args)
} else {
result = thisArg.fn()
}
delete thisArg.fn
return result
}
bind的實(shí)現(xiàn)原理比call和apply要復(fù)雜一些,bind中需要考慮一些復(fù)雜的邊界條件。bind后的函數(shù)會(huì)返回一個(gè)函數(shù),而這個(gè)函數(shù)也可能被用來實(shí)例化:
Function.prototype.bind = function(thisArg) {
if(typeof this !== 'function'){
throw new TypeError(this + 'must be a function');
}
// 存儲(chǔ)函數(shù)本身
const _this = this;
// 去除thisArg的其他參數(shù) 轉(zhuǎn)成數(shù)組
const args = [...arguments].slice(1)
// 返回一個(gè)函數(shù)
const bound = function() {
// 可能返回了一個(gè)構(gòu)造函數(shù),我們可以 new F(),所以需要判斷
if (this instanceof bound) {
return new _this(...args, ...arguments)
}
// apply修改this指向,把兩個(gè)函數(shù)的參數(shù)合并傳給thisArg函數(shù),并執(zhí)行thisArg函數(shù),返回執(zhí)行結(jié)果
return _this.apply(thisArg, args.concat(...arguments))
}
return bound
}