js中的new关键字
前言
使用常规的{}
花括号可以创建一个对象,但是当我们想要创建相似的对象时,如果还使用{}
就会产生很多冗余的代码,所以为了方便,js就设计了new
关键字,我们可以对构造函数使用new
操作符来创建一类相似的对象。
构造函数
new
构造函数在技术上是常规函数。不过有两个约定:
- 它们的命名通常以大写字母开头。
- 它们只能由
"new"
操作符来执行。
约定就是我们的代码书写习惯,应该尽量遵循。
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
console.log(user.name); // Jack
console.log(user.isAdmin); // false
当一个函数被使用 new
操作符执行时,它按照以下步骤:
- 一个新的空对象被创建并分配给
this
。 - 函数体执行。通常它会修改
this
,为其添加新的属性。 - 返回
this
的值。
换句话说,new User(...)
做的就是类似的事情:
function User(name) {
// this = {};(隐式创建)
// 添加属性到 this
this.name = name;
this.isAdmin = false;
// return this;(隐式返回)
}
所以 new User("Jack")
的结果是相同的对象:
let user = {
name: "Jack",
isAdmin: false
};
现在,如果我们想创建其他用户,我们可以调用 new User("Ann")
,new User("Alice")
等。比每次都使用字面量创建要短得多,而且更易于阅读。
这是构造器的主要目的 —— 实现可重用的对象创建代码。
🚩从技术上讲,任何函数(除了箭头函数,它没有自己的
this
)都可以用作构造器。即可以通过new
来运行,它会执行上面的算法。“首字母大写”是一个共同的约定,以明确表示一个函数将被使用new
来运行。
new.target(了解,不常用)
在一个函数内部,我们可以使用 new.target
属性来检查它是否被使用 new
进行调用了。
对于常规调用,它为空,对于使用 new
的调用,则等于该函数:
function User() {
console.log(new.target);
}
// 不带 "new":
User(); // undefined
// 带 "new":
new User(); // function User { ... }
它可以被用在函数内部,来判断该函数是被通过 new
调用的“构造器模式”,还是没被通过 new
调用的“常规模式”。
我们也可以让 new
调用和常规调用做相同的工作,像这样:
function User(name) {
if (!new.target) { // 如果你没有通过 new 运行我
return new User(name); // ……我会给你添加 new
}
this.name = name;
}
let john = User("John"); // 将调用重定向到新用户
console.log(john.name); // John
这种方法有时被用在库中以使语法更加灵活。这样人们在调用函数时,无论是否使用了 new
,程序都能工作。
不过,到处都使用它并不是一件好事,因为省略了 new
使得很难观察到代码中正在发生什么。而通过 new
我们都可以知道这创建了一个新对象。
构造器的return
通常,构造器没有 return
语句。它们的任务是将所有必要的东西写入 this
,并自动转换为结果。
但是,如果这有一个 return
语句,那么规则就简单了:
- 如果
return
返回的是一个对象,则返回这个对象,而不是this
。 - 如果
return
返回的是一个原始类型,则忽略。
换句话说,带有对象的 return
返回该对象,在所有其他情况下返回 this
。
例如,这里 return
通过返回一个对象覆盖 this
:
function BigUser() {
this.name = "John";
return { name: "Godzilla" }; // <-- 返回这个对象
}
console.log(new BigUser().name); // Godzilla,得到了那个对象
这里有一个 return
为空的例子(或者我们可以在它之后放置一个原始类型,没有什么影响):
function SmallUser() {
this.name = "John";
return; // <-- 返回 this
}
console.log(new SmallUser().name); // John
通常构造器没有 return
语句。这里我们主要为了完整性而提及返回对象的特殊行为。
✈省略括号
顺便说一下,如果没有参数,我们可以省略
new
后的括号:jslet user = new User; // <-- 没有参数 // 等同于 let user = new User();
这里省略括号不被认为是一种“好风格”,但是规范允许使用该语法。
构造器中的方法
使用构造函数来创建对象会带来很大的灵活性。构造函数可能有一些参数,这些参数定义了如何构造对象以及要放入什么。
当然,我们不仅可以将属性添加到 this
中,还可以添加方法。
例如,下面的 new User(name)
用给定的 name
和方法 sayHi
创建了一个对象:
function User(name) {
this.name = name;
this.sayHi = function () {
console.log("My name is: " + this.name);
};
}
let john = new User("John");
john.sayHi(); // My name is: John
/*
john = {
name: "John",
sayHi: function() { ... }
}
*/
手写一个new
function myNew() {
// 1、创建一个空对象
let obj = new Object()
let constructor = [].shift.call(arguments) // 获取构造方法
// 2、将新对象的原型指向 构造方法的prototype上
obj.__proto__ = constructor.prototype
// 3、获取到构造方法的返回值(如果原先构造方法有返回值,且是对象,那么原始的new会把这个对象返回出去,基本类型会忽略)
let ret = constructor.apply(obj, arguments) // 这里的arguments的第一个参数已经在最开始被shift了,所以剩下的参数全都是构造方法需要的值
// (ret || obj)是为了判断null,当为null时,也返回新对象
return typeof ret === 'object' ? (ret || obj) : obj
}
使用
function Person(name, age) {
this.name = name
this.age = age
}
let p = myNew(Person, 'cheny', 28)
console.log(p instanceof Person);
总结
对构造函数使用
new
关键字会产生一个新对象。使用
new
时可以大致看成下面几步jsfunction User(name) { // 1、this = {};(隐式创建) // 2、this.__proto__ = User.prototype // 3、添加属性到 this this.name = name; this.isAdmin = false; // 4、return this;(隐式返回) }
如果构造函数有返回值时,新对象有两种情况:
- 如果
return
返回的是一个对象,则返回这个对象,而不是this
。 - 如果
return
返回的是一个原始类型,则忽略。
- 如果
手写
new
时,应注意将新对象的__proto__
指向构造方法的prototype
上。