Rest语法 和 Spread语法
Rest语法让函数可以在调用时传入任意数量参数
Rest语法使用如下:
1 | function sumAll(...args) { // 数组名为 args |
❗️Rest 参数必须放到参数列表的末尾
Rest 参数会收集剩余的所有参数,因此下面这种用法没有意义,并且会导致错误:
1 | function f(arg1, ...rest, arg2) { // arg2 在 ...rest 后面?! |
...rest 必须写在参数列表最后。
Spread语法与Rest语法作用相反,他的作用是将数组对象中的内容展开到函数中
例子如下:
1 | let arr = [3, 5, 1]; |
当我们在代码中看到 "..." 时,它要么是 rest 参数,要么是 spread 语法。
有一个简单的方法可以区分它们:
- 若
...出现在函数参数列表的最后,那么它就是 rest 参数,它会把参数列表中剩余的参数收集到一个数组中。 - 若
...出现在函数调用或类似的表达式中,那它就是 spread 语法,它会把一个数组展开为列表。
使用场景:
- Rest 参数用于创建可接受任意数量参数的函数。
- Spread 语法用于将数组传递给通常需要含有许多参数的函数。
...args 和 arguments
arguments和...args(剩余参数)都是在JavaScript函数中使用的,用于处理函数的参数,但它们的工作方式有所不同。
-
arguments:这是一个类数组对象,它包含了函数调用时传递给函数的所有参数。arguments对象不是一个真正的数组,所以它没有数组的方法,如push、pop或forEach。arguments在所有函数中都可用,不仅仅是在箭头函数中。1
2
3
4
5
6
7
8
9
10
11function testArguments() {
console.log(arguments[0]); // 输出 "first"
console.log(arguments[1]); // 输出 "second"
console.log(arguments.length); // 输出 2
}
testArguments('first', 'second'); -
...args(剩余参数):这是ES6引入的一个新特性,它将一个不定数量的参数表示为一个数组。与arguments不同,...args是一个真正的数组,所以你可以在它上面使用所有的数组方法。...args只能在函数的最后一个参数中使用。1
2
3
4
5
6
7
8
9function testRestParameters(...args) {
console.log(args[0]); // 输出 "first"
console.log(args[1]); // 输出 "second"
console.log(args.length); // 输出 2
}testRestParameters('first', 'second');
总的来说,
...args(剩余参数)是一个更现代的、更强大的方式来处理函数的参数,它提供了比arguments更多的功能和更好的可读性。
func.apply 和 func.call
-
func.apply:这个方法接受两个参数。第一个参数是要绑定到this的值,第二个参数是一个数组,数组中的元素将作为参数传递给函数。1
2
3
4
5
6
7function greet(name, age) {
console.log(`Hello, my name is ${name} and I am ${age} years old.`);
}
greet.apply(null, ['John', 25]); // 输出 "Hello, my name is John and I am 25 years old."在这个例子中,
greet.apply(null, ['John', 25])调用了greet函数,this被绑定到null,'John'和25作为参数传递给greet函数。 -
func.call:这个方法接受一个或多个参数。第一个参数是要绑定到this的值,后面的参数将作为参数传递给函数。1
2
3
4
5function greet(name, age) {
console.log(`Hello, my name is ${name} and I am ${age} years old.`);
}
greet.call(null, 'John', 25); // 输出 "Hello, my name is John and I am 25 years old."在这个例子中,
greet.call(null, 'John', 25)调用了greet函数,this被绑定到null,'John'和25作为参数传递给greet函数。总的来说,
func.apply和func.call的主要区别在于如何传递参数:func.apply接受一个参数数组,而func.call接受一个参数列表。
prototype 和 [[Prototype]] 和 _proto_
-
prototype:这是函数的一个属性。每个函数都有一个prototype属性,它指向一个对象(称为原型对象)。当你使用new关键字创建一个新的对象时,新对象的[[Prototype]]属性会被设置为构造函数的prototype属性所指向的对象。这就是新对象如何继承构造函数原型上的方法和属性的。1
2
3
4
5
6
7function MyConstructor() {}
MyConstructor.prototype.sayHello = function() {
console.log('Hello!');
};
let myObject = new MyConstructor();
myObject.sayHello(); // 输出 "Hello!"在这个例子中,
MyConstructor.prototype是一个对象,它有一个sayHello方法。当我们创建一个新的MyConstructor对象时,这个对象的[[Prototype]]属性被设置为MyConstructor.prototype,所以它可以访问sayHello方法。 -
[[Prototype]]:这是每个对象都有的一个内部属性(在ES6之前,这个属性被称为__proto__),它指向该对象的原型。你不能直接访问[[Prototype]],但你可以使用Object.getPrototypeOf()和Object.setPrototypeOf()方法来获取和设置一个对象的原型。1
2let obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype); // 输出 true在这个例子中,
Object.getPrototypeOf(obj)返回的是obj对象的原型,即Object.prototype。 -
__proto__:这是每个对象都有的一个属性,它指向该对象的原型,相当于[[Prototype]]的getter和setter。你可以通过__proto__属性来访问和修改一个对象的原型。然而,__proto__属性已经被弃用,并且在某些情况下可能会导致性能问题。因此,现代的JavaScript代码应该避免使用__proto__,而应该使用Object.getPrototypeOf()和Object.setPrototypeOf()方法来获取和设置一个对象的原型。
Class和new Function()
在JavaScript中,class和new Function()都可以用来创建对象,但它们之间存在一些重要的区别。
-
class:class是ES6引入的新语法,用于创建对象和实现面向对象编程的特性,如继承。class语法更清晰,更易于理解和使用。在类中,你可以定义构造函数(constructor)和其他方法。类默认使用严格模式(strict mode),并且不可被重新声明。class User {...}构造实际上做了如下的事儿:- 创建一个名为
User的函数,该函数成为类声明的结果。该函数的代码来自于constructor方法(如果我们不编写这种方法,那么它就被假定为空)。
1
2
3
4
5
6class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// 佐证:User 是一个函数
alert(typeof User); // function- 存储类中的方法,例如
User.prototype中的sayHi。
1
2
3
4
5
6
7
8
9
10
11
12class MyClass {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, ${this.name}!`);
}
}
let obj = new MyClass('John');
obj.sayHello(); // 输出 'Hello, John!'虽然很多人说
class是一种语法糖,但实际上它与Function有重大差异-
首先,通过
class创建的函数具有特殊的内部属性标记[[IsClassConstructor]]: true。因此,它与手动创建并不完全相同。编程语言会在许多地方检查该属性。例如,与普通函数不同,必须使用
new来调用它:1
2
3
4
5
6class User {
constructor() {}
}
alert(typeof User); // function
User(); // Error: Class constructor User cannot be invoked without 'new'此外,大多数 JavaScript 引擎中的类构造器的字符串表示形式都以 “class…” 开头
1
2
3
4
5class User {
constructor() {}
}
alert(User); // class User { ... }还有其他的不同之处,我们很快就会看到。
-
类方法不可枚举。 类定义将
"prototype"中的所有方法的enumerable标志设置为false。这很好,因为如果我们对一个对象调用
for..in方法,我们通常不希望 class 方法出现。 -
类总是使用
use strict。 在类构造中的所有代码都将自动进入严格模式。
- 创建一个名为
-
new Function():new Function()是一种创建新函数的方式。这种方式的特点是,你可以在运行时动态地创建函数,因为函数的主体是通过字符串来定义的。但是,这种方式的缺点是,代码可能难以阅读和维护,而且可能存在安全风险,因为它允许执行任意的JavaScript代码。1
2
3
4
5
6
7
8let MyFunc = new Function('name', `this.name = name;`);
MyFunc.prototype.sayHello = function() {
console.log(`Hello, ${this.name}!`);
};
let obj = new MyFunc('John');
obj.sayHello(); // 输出 'Hello, John!'