new的时候做了什么?
为什么要有new
我认为了解一个api怎么用之前,更重要的是要知道这个api为什么因何而诞生的!
JS的诞生就是为了快速的解决问题的。所以它的语法很多时候都是为了让用户省心而设计的,比如弱语法,比如这个new。
在我平时写的业务中,其实没有创造大量对象的场景。所以这里以游戏的业务为例子。我记得微信小游戏的DEMO就是飞机大战,现在我们给敌机设计它的属性。
对于敌机而言,它有不同的机种,不同的血量,可以发送子弹,可以向屏幕下方移动。在代码中如下:
const 敌机 = {
key: 0001, // 唯一ID
机种: 'a', // 非boss
攻击范围: 1000,
发射: function() {
// 发射子弹
},
向下移动: function() {
// 冲向我方
}
}
这是一辆飞机的对象。但是在飞机大战当中,敌机可是满屏都有的。所以我们需要很多辆这样的飞机:
const 敌机s = []
for (let i = 0; i< 50; i++) {
let 敌机 = {
key: '敌机' + i, // 唯一ID
机种: '歼20', // 非boss
攻击范围: 1000,
发射: function() {
// 发射子弹
},
向下移动: function() {
// 冲向我方
}
}
敌机s.push(敌机)
}
上面对象数组中的发射和向下移动是完全一样的函数 ,但是被创造了50遍。机种和攻击范围是固定,只有key值各不相同。
所以我们可以有如下的改造:
const 敌机原型 = {
兵种: "歼20",
攻击范围: 1000,
发射: function() {
// 发射子弹
},
向下移动: function() {
// 冲向我方
}
}
const 敌机s = []
for(let i = 0; i < 50; i++) {
let 敌机 = {
key: '敌机' + i,
}
敌机.[[proto]] = 敌机原型
敌机s.push(敌机)
}
是不是有点‘构造’函数那个味了?循环里面我们做了两件事情,创建对象,以及处理对象,干了两件事情,敌机的对象也分开来了。有了更好的复用,我们可以使用函数来封装:
function 敌机(key){
var 临时对象 = {}
临时对象.[[proto]] = 敌机.原型
临时对象.key = '敌机' + key
return 临时对象
}
敌机.原型 = {
兵种: "歼20",
攻击范围: 1000,
发射: function() {
// 发射子弹
},
向下移动: function() {
// 冲向我方
}
}
随后在创建敌机们的时候,可以直接调用上面的函数:
const 敌机s = []
for(let i = 0; i < 100; i++) {
敌机s.push(敌机(i))
}
回到我说的第一句话,它的语法很多时候都是为了让用户省心而设计的。
它的这个省心体现在哪里呢?
悄悄这位慈祥的老人。他为了让我更快的创建对象,通过一个 new
关键字为我们省略了四个步骤!
当函数return不同类型的时候
通俗的知道了new的意义,我们再来看它在这个过程中做了什么就容易很多了。
回想一开始学习JS的时候,好好的函数,非要弄一个构造函数的称呼出来。
我第一次看到构造函数的时候,郁闷了好久,为什么函数还分这么类型,匿名函数、立即执行函数、构造函数。
给我造成了多年的记忆负担!函数就是函数,不能因为运行函数的时身边的关键字不一样就给它瞎起名字。
所以就应该是new一个函数,而不是new一个构造函数。
在new的过程中主要就是分为两种情况,根据函数return的是基础类型还是引用类型,返回不同的结果。
return 基础类型
当使用new
关键字调用一个函数时,函数中return的是一个基础类型。JavaScript引擎将执行以下步骤:
- 创建一个新的空对象。
- 将新创建的对象的原型指向构造函数的原型对象。
- 将构造函数的this关键字绑定到新创建的对象上。
- 执行构造函数中的代码,将属性和方法添加到新的对象中。
- 如果构造函数返回一个对象,则返回该对象,否则返回新创建的对象。
下面是一个简单的例子,演示了使用new
关键字创建对象实例的过程:
// 定义一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 在Person的原型对象上添加一个方法
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
// 使用new关键字创建对象实例
const person1 = new Person('John', 30);
person1.sayHello(); // 输出: Hello, my name is John and I am 30 years old.
在上面的例子中,我们定义了一个Person
构造函数,它有两个参数:name
和age
。然后,我们在Person
的原型对象上添加了一个sayHello
方法,该方法输出一个字符串,包含对象的name
和age
属性。最后,我们使用new
关键字调用Person
构造函数,并将结果赋值给person1
变量。最后,我们调用person1
对象的sayHello
方法,输出一个字符串。
通过这个例子,我们可以看到,使用new
关键字创建对象实例时,JavaScript引擎会将新创建的对象的原型指向构造函数的原型对象,因此我们可以在构造函数的原型对象上定义方法,并在新创建的对象上使用这些方法。
众所周知,函数不写return的时候,它返回的是undefined, 也是基础类型
return 引用类型
return 引用类型就简单很多,return的对象会直接'覆盖'JS引擎内部生成的 "this" 对象。如下代码片段:
function Person(name) {
this.name = name
return {
name: 'Greg'
}
}
Person.prototype.say = function() {
console.log('yoran')
}
const p = new Person('John')
console.log(p.name) // Greg
总结一下
-
new一个对象的过程
- 创建一个空对象
- 给这个空对象添加
__proto__
属性,并指向prototype
原型对象 - 将this的属性绑定到这个对象当中
- 然后
return
这个对象给实例化对象
-
return
的是基本类型和对象会带来不同的效果return
的是数字的话, 不会有什么变化,直接走以前的return
的是对象的话, 会直接覆盖你上面的那一连串骚操作
手写一个new方法
function targetFn(name) {
this.name = name
}
targetFn.prototype = {
aget: 18
}
function myNew(fn, ...rest) {
const newObj = Object.create(fn.prototype)
const returnResult = fn.apply(newObj, rest)
return isObject(returnResult)? returnResult: newObj
}
function isObject(obj) {
const target = '[object Object]'
return Object.prototype.toString.call(obj) === target
}