likes
comments
collection
share

new的时候做了什么?

作者站长头像
站长
· 阅读数 8

为什么要有new

我认为了解一个api怎么用之前,更重要的是要知道这个api为什么因何而诞生的!

JS的诞生就是为了快速的解决问题的。所以它的语法很多时候都是为了让用户省心而设计的,比如弱语法,比如这个new。

new的时候做了什么?

在我平时写的业务中,其实没有创造大量对象的场景。所以这里以游戏的业务为例子。我记得微信小游戏的DEMO就是飞机大战,现在我们给敌机设计它的属性。

new的时候做了什么?

对于敌机而言,它有不同的机种,不同的血量,可以发送子弹,可以向屏幕下方移动。在代码中如下:

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的时候做了什么?

new的时候做了什么?

悄悄这位慈祥的老人。他为了让我更快的创建对象,通过一个 new 关键字为我们省略了四个步骤!

当函数return不同类型的时候

通俗的知道了new的意义,我们再来看它在这个过程中做了什么就容易很多了。

回想一开始学习JS的时候,好好的函数,非要弄一个构造函数的称呼出来。

我第一次看到构造函数的时候,郁闷了好久,为什么函数还分这么类型,匿名函数、立即执行函数、构造函数。

new的时候做了什么?

给我造成了多年的记忆负担!函数就是函数,不能因为运行函数的时身边的关键字不一样就给它瞎起名字。

所以就应该是new一个函数,而不是new一个构造函数。

在new的过程中主要就是分为两种情况,根据函数return的是基础类型还是引用类型,返回不同的结果。

return 基础类型

当使用new关键字调用一个函数时,函数中return的是一个基础类型。JavaScript引擎将执行以下步骤:

  1. 创建一个新的空对象。
  2. 将新创建的对象的原型指向构造函数的原型对象。
  3. 将构造函数的this关键字绑定到新创建的对象上。
  4. 执行构造函数中的代码,将属性和方法添加到新的对象中。
  5. 如果构造函数返回一个对象,则返回该对象,否则返回新创建的对象。

下面是一个简单的例子,演示了使用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构造函数,它有两个参数:nameage。然后,我们在Person的原型对象上添加了一个sayHello方法,该方法输出一个字符串,包含对象的nameage属性。最后,我们使用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一个对象的过程

    1. 创建一个空对象
    2. 给这个空对象添加__proto__属性,并指向prototype原型对象
    3. 将this的属性绑定到这个对象当中
    4. 然后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
}