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上。