定义函数的方式(4种)
1. 函数声明
function 方法名("参数1", "参数2", ...) {}
2. 函数表达式
let 变量 = function("参数1", "参数2", ...) { };
3. 箭头函数
let 变量 = ("参数1", "参数2", ...) => {}
4. 使用Function构造函数(不推荐)
let 变量 = new Function("参数1", "参数2", ..., "函数体")
函数声明与函数表达式的区别
- 以函数声明的方法定义的函数,函数名是必须的,而函数表达式的函数名是可选的。
- 函数声明在JS解析时存在函数声明提升,函数表达式不存在
箭头函数与普通函数区别及不适用场合
区别
1. 箭头函数的this指向
(1) 箭头函数没有prototype(原型),所以箭头函数本身没有this
// 箭头函数
let a = () => {};
console.log(a.prototype); // undefined
// 普通函数
function a() {};
console.log(a.prototype); // {constructor:f}
(2) 箭头函数this指向继承自外层第一个普通函数的this
function King() {
this.name = 'Henry'
setTimeout(() => {
console.log(this.name);
}, 10)
}
new King() // Henry
(3) 不能通过call、apply和bind直接修改箭头函数的this指向
// 外层不存在普通函数时,没法修改this指向,指向window
var id = 10;
let fun = () => {
console.log(this.id)
};
fun(); // 10
fun.call({ id: 20 }); // 10
fun.apply({ id: 20 }); // 10
fun.bind({ id: 20 })(); // 10
// 外层存在普通函数时,去修改被继承的普通函数的this指向,然后箭头函数的this指向也会跟着改变
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 }); // id: 42
(4) 箭头函数外层没有普通函数,严格模式和非严格模式下它的this都会指向window(全局对象)
2. 箭头函数没有自己的arguments
(1) 箭头函数的this指向全局对象时,使用arguments会报未声明的错误
let b = () => {
console.log(arguments);
};
b(1, 2, 3, 4); // Uncaught ReferenceError: arguments is not define
(2) 箭头函数的this指向普通函数时,它的arguments继承于该普通函数
function bar() {
console.log(arguments); // ['外层第二个普通函数的参数']
function bb() {
console.log(arguments); // ["外层第一个普通函数的参数"]
let a = () => {
console.log(arguments, 'arguments继承this指向的那个普通函数'); // ["外层第一个普通函数的参数"]
};
a('箭头函数的参数'); // this指向bb
}
bb('外层第一个普通函数的参数');
}
bar('外层第二个普通函数的参数');
那么应该如何来获取箭头函数不定数量的参数呢?答案是:ES6的rest参数(...扩展符)
rest参数获取函数的多余参数
这是ES6的API,用于获取函数不定数量的参数数组,这个API是用来替代arguments的,API用法如下:
let a = (first, ...abc) => {
console.log(first, abc); // 1 [2, 3, 4]
};
a(1, 2, 3, 4);
rest参数有两点需要注意:
- rest必须是函数的最后一位参数:
let a = (first, ...rest, three) => {
console.log(first, rest,three); // 报错:Rest parameter must be last formal parameter
};
a(1, 2, 3, 4);
- 函数的length属性,不包括rest参数
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
3. 使用new调用函数会报错(也就是箭头函数不能作为构造函数使用)
无论箭头函数的this指向哪里,使用new调用箭头函数都会报错,因为箭头函数没有constructor
let A = () => {};
let b = new A(); // A is not a constructor
4. 箭头函数不支持new.target
new.tartget是ES6新增的属性,用于检测函数是否使用new关键字调用的。
如果函数是正常调用的,则new.target的值是undefined; 如果是使用new关键字调用的,则new.target将返回被调用的构造函数的引用。
- 箭头函数的this指向全局对象,在箭头函数中使用箭头函数会报错
let a = () => {
console.log(new.target); // 报错:new.target 不允许在这里使用
};
a();
- 箭头函数的this指向普通函数,它的new.target就是指向该普通函数的引用。
function B() {
let a = () => {
console.log(new.target); // 指向函数B:function B(){...}
};
a();
}
new B()
5. 箭头函数不支持重命名函数参数,普通函数支持重命名函数参数
如示例所示,普通函数支持重命名函数参数,后面出现的会覆盖前面的,箭头函数会抛出错误
function func1(a, a) {
console.log(a, arguments); // 2 [1,2]
}
let func2 = (a,a) => {
console.log(a); // 报错:在此上下文中不允许重复参数名称
};
func1(1, 2); func2(1, 2);
6. 箭头函数相对于普通函数语法更简洁优雅
- 箭头函数都是匿名函数,并且都不用写function
- 只有一个参数的时候可以省略括号
let f = a => a; // 传入a 返回a
- 函数只有一条语句时可以省略{}和return
let f = (a, b, c) => a;
- 简化回调函数写法,让你的回调函数更优雅
[1,2,3].map(function (x) {
return x * x;
}); // 普通函数写法
[1,2,3].map(x => x * x); // 箭头函数只需要一行
7. 箭头函数不能用作Generator函数,不能使用yield关键字
总结:
- 箭头函数没有prototype(原型),所以箭头函数本身没有this
- 箭头函数的this在定义的时候继承自外层第一个普通函数的this。
- 如果箭头函数外层没有普通函数,严格模式和非严格模式下它的this都会指向window(全局对象)
- 箭头函数本身的this指向不能改变,但可以修改它要继承的对象的this。
- 箭头函数的this指向全局对象时,使用arguments会报未声明的错误。
- 箭头函数的this指向普通函数时,它的argumens继承于该普通函数
- 使用new调用箭头函数会报错,因为箭头函数没有constructor
- 箭头函数不支持new.target
- 箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名
- 箭头函数相对于普通函数语法更简洁优雅
箭头函数不适用场合
1. 第一个场合是定义对象的方法,且该方法内部包括this
const cat = {
lives: 9,
jumps: () => {
this.lives--;
}
}
上面代码中,cat.jumps()方法是一个箭头函数,这是错误的。
调用cat.jump()时,如果是普通函数,该方法内部的this指向cat;但写成上面那样的箭头函数,this便指向全局对象,因此不会得到预期结果。
2. 第二个场合是需要动态this的时候,也不应该使用箭头函数
var button = document.getElementById('press');
button.addEventListener('click', () => {
this.classList.toggle('on');
});
上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。