# 前言
今天在写代码时,用了箭头函数,出现了如下问题:
function Drawing(shape) { | |
this.shape = shape | |
} | |
Drawing.prototype.drawShape = (radius, x, y) => { //bug 问题所在 | |
return this.shape.draw(radius, x, y) //this 指向了全局 | |
} |
报错信息:
Error in created hook: "TypeError: Cannot read properties of undefined (reading 'shape')"
然后,改成 function
后,错误消除。
function Drawing(shape) { | |
this.shape = shape | |
} | |
Drawing.prototype.drawShape = function(radius, x, y) { | |
return this.shape.draw(radius, x, y) | |
} |
原因是 this 指向问题,这是因为不了解箭头函数属性导致的。
- 箭头函数没有单独的
this
、arguments
、super
,并且不能作为构造函数。
# 箭头函数
# 箭头函数
- 箭头函数没有单独的
this
、arguments
、super
,并且不能作为构造函数。 - 虽然箭头函数没有创建自己的 this,但是它会在自己的作用域链上继承 this,不能修改 this 的指向,所以在
call
和apply
调用时,只能传参数,不能绑定this
。 - 箭头函数没有
prototype
属性 - 箭头函数不能作为函数生成器
# 普通函数
- 普通函数可以作为构造函数,this 指针指向一个新的对象。
- 如果普通函数是一个对象的方法,那么 this 指向的是该对象。
# 普通函数和箭头函数的区别:
- 箭头函数没有
prototype
(原型),所以箭头函数本身没有 this- 箭头函数的 this 在定义的时候继承自外层第一个普通函数的 this。
- 如果箭头函数外层没有普通函数,严格模式和非严格模式下它的 this 都会指向
window
(全局对象)- 箭头函数本身的 this 指向不能改变,但可以修改它要继承的对象的 this。
- 箭头函数的 this 指向全局,使用 arguments 会报未声明的错误。
- 箭头函数的 this 指向普通函数时,它的
argumens
继承于该普通函数- 使用
new
调用箭头函数会报错,因为箭头函数没有constructor
- 箭头函数不支持
new.target
- 箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名
- 箭头函数相对于普通函数语法更简洁优雅
# 箭头函数的注意事项及不适用场景
箭头函数的注意事项:
箭头函数一条语句返回对象字面量,需要加括号
var func2 = () => ({foo: 1}) // ok
箭头函数在参数和箭头之间不能换行
箭头函数的解析顺序相对
||
靠前
let c = false || (() => {}); // ok
# 箭头函数的特点
# 1. 相比普通函数,箭头函数有更加简洁的语法。
普通函数
function add(num) { | |
return num + 10 | |
} |
箭头函数
const add = num => num + 10; |
# 2. 箭头函数不绑定 this,会捕获其所在上下文的 this,作为自己的 this。
这句话需要注意的是,箭头函数的外层如果有普通函数,那么箭头函数的 this 就是这个外层的普通函数的 this,箭头函数的外层如果没有普通函数,那么箭头函数的 this 就是全局变量。
下面这个例子是箭头函数的外层有普通函数。
let obj = { | |
fn:function(){ | |
console.log('我是普通函数',this === obj) // true | |
return ()=>{ | |
console.log('我是箭头函数',this === obj) // true | |
} | |
} | |
} | |
console.log(obj.fn()()) |
下面这个例子是箭头函数的外层没有普通函数。
let obj = { | |
fn:()=>{ | |
console.log(this === window); | |
} | |
} | |
console.log(obj.fn()) | |
// true |
# 3. 箭头函数是匿名函数,不能作为构造函数,不可以使用 new 命令,否则后抛出错误。
let f = () => {}; | |
let t = new f(); // f is not a constructor |
# 4. 箭头函数不绑定 arguments
,取而代之用 rest 参数解决,同时没有 super
和 new.target
。
箭头函数没有
arguments
、super
、new.target
的绑定,这些值由外围最近一层非箭头函数决定。
下面的这个函数会报错,在浏览器环境下。
let f = ()=>console.log(arguments); | |
// 报错 | |
f(); // arguments is not defined |
下面的箭头函数不会报错,因为
arguments
是外围函数的。
function fn(){ | |
let f = ()=> { | |
console.log(arguments) | |
} | |
f(); | |
} | |
fn(1,2,3) // [1,2,3] |
箭头函数可以通过拓展运算符获取传入的参数。
const testFunc = (...args) => { | |
console.log(args); // [ [1, 2, 3] ], undefined | |
} | |
testFunc([1, 2, 3]) |
# 5. 使用 call,apply,bind 并不会改变箭头函数中的 this 指向。
- 当对箭头函数使用 call 或 apply 方法时,只会传入参数并调用函数,并不会改变箭头函数中 this 的指向。
- 当对箭头函数使用 bind 方法时,只会返回一个预设参数的新函数,并不会改变这个新函数的 this 指向。
请看下面的代码
window.name = "window_name"; | |
let f1 = function () { | |
return this.name; | |
}; | |
let f2 = () => this.name; | |
let obj = { name: "obj_name" }; | |
console.log(f1.call(obj)); //obj_name | |
console.log(f2.call(obj)); // window_name | |
console.log(f1.apply(obj)); // obj_name | |
console.log(f2.apply(obj)); // window_name | |
console.log(f1.bind(obj)()); // obj_name | |
console.log(f2.bind(obj)()); // window_name |
# 6. 箭头函数没有原型对象 prototype 这个属性
由于不可以通过 new 关键字调用,所以没有构建原型的需求,所以箭头函数没有
prototype
这个属性。
let F = ()=>{}; | |
console.log(F.prototype) // undefined |
# 7. 不能使用 yield 关键字,不能用作 Generator 函数
# arguments 辨析
既然上文我们提到了
arguments
,那么下面我们就仔细讲讲这个arguments
。
# arguments 有什么用?
arguments
对象是所有非箭头函数中都可用的局部变量,可以使用arguments
对象在函数中引用函数的参数,此对象包含传递给函数的每一个参数,第一个参数在索引 0 的位置。
# 如何将 arguments 对象转换为数组
- 通过
slice
- 通过拓展运算符
- 通过
Array.from
var args = Array.prototype.slice.call(arguments); | |
var args = [].slice.call(arguments); | |
const args = Array.from(arguments); | |
const args = [...arguments]; |
# arguments 函数如何调用自身函数?
我们先看看下面这个函数,这个是可以正常运行的。
function factorial (n) { | |
return !(n > 1) ? 1 : factorial(n - 1) * n; | |
} | |
[1,2,3,4,5].map(factorial); |
但是作为匿名函数则不行。
[1,2,3,4,5].map(function (n) { | |
return !(n > 1) ? 1 : /* what goes here? */ (n - 1) * n; | |
}); |
因此
arguments.callee
诞生了。
arguments
要想调用自身的匿名函数,可以通过arguments.callee
来调用。
[1,2,3,4,5].map(function (n) { | |
return !(n > 1) ? 1 : arguments.callee(n - 1) * n; | |
}); |
然而,由于一些安全和性能问题,
arguments.callee
已经被弃用,并且在严格模式下会导致错误。用如下方法代替。
[1,2,3,4,5].map(function myArg(n) { | |
return !(n > 1) ? 1 : myArg(n - 1) * n; | |
}); |