JavaScript中 new 一个对象过程详解

简介: JavaScript中 new 一个对象过程详解

要创建 Person 的新实例,必须使用 new 操作符。以这种方式调用构造函数实际上会经历以下 4个步骤:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
  3. 执行构造函数中的代码(为这个新对象添加属性);
  4. 返回新对象;

1. new 操作符

在有上面的基础概念的介绍之后,在加上 new 操作符,我们就能完成传统面向对象的class + new的方式创建对象,在JavaScript中,我们将这类方式成为 Pseudoclassical。

基于上面的例子,我们执行如下代码

var obj = new Base();

这样代码的结果是什么,我们在Javascript引擎中看到的对象模型是:

2020062310470442.png

new操作符具体干了什么呢? 其实很简单,就干了三件事情。

var obj  = {};
obj.proto = Base.prototype;
Base.call(obj);
第一行,我们创建了一个空对象obj;
第二行,我们将这个空对象的 __proto__ 成员指向了 Base 函数对象 prototype 成员对象;
第三行,我们将 Base 函数对象的 this 指针替换成obj,然后再调用 Base 函数,于是我们就给 obj 对象赋值了一个 id 成员变量,
这个成员变量的值是 ”base” ,关于 call 函数的用法。

如果我们给 Base.prototype 的对象添加一些函数会有什么效果呢?

Base.prototype.toString = function() {
   return this.id;
}

那么当我们使用 new 创建一个新对象的时候,根据 proto 的特性,toString 这个方法也可以做新对象的方法被访问到。

于是我们看到了:

构造子对象中,我们来设置‘类’的成员变量(例如:例子中的id),构造子对象 prototype 中我们来设置 ‘类’ 的公共方法。于是通过函数对象和Javascript特有的 __ proto __ 与 prototype 成员及 new 操作符,模拟出类和类实例化的效果。

2. new操作中发生了什么

对于大部分前端开发者而言,new 一个构造函数或类得到对应实例,是非常普遍的操作了。

下面的例子中分别通过 构造函数 与 class类 实现了一个简单的创建实例的过程。

// ES5构造函数
let Dog = function (name, age) {
  this.name = name;
  this.age = age;
};
Dog.prototype.sayName = function () {
  console.log(this.name);
};
const myDog = new Dog('汪汪', 2);
myDog.sayName() // '汪汪'
// ES6 class类
class Cat {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  sayName() {
    console.log(this.name);
  }
};
const myCat = new Cat('QIU', 3);
myCat.sayName(); // QIU

但 new 不应该像一个黑盒,我们除了知道结果,更应该明白过程究竟如何。


比较直观的感觉,当我们 new 一个构造函数,得到的实例继承了构造器的构造属性( this.name 这些) 以及原型上的属性。

在《JavaScript模式》这本书中,new 的过程说的比较直白。当我们 new 一个构造器,主要有三步:

  1. 创建一个空对象,将它的引用赋给 this,继承函数的原型;
  2. 通过 this 将属性和方法添加至这个对象;
  3. 最后返回 this 指向的新对象,也就是实例(如果没有手动返回其他的对象);

改写上面的例子,大概就是这样:

// ES5构造函数
let Dog = function (name, age) {
  // 1.创建一个新对象,赋予this,这一步是隐性的,
  //   let this = {};
  // 2.给this指向的对象赋予构造属性
  this.name = name;
  this.age = age;
  // 3.如果没有手动返回对象,则默认返回this指向的这个对象,也是隐性的
  // return this;
};
const myDog = new Dog();

这应该不难理解,你应该在工作中看过类似下述代码中的操作,将this赋予一个新的变量(例如 that ),最后返回这个变量

// ES5构造函数
let Dog = function (name, age) {
  let that = this;
  that.name = name;
  that.age = age;
  return that;
};
const myDog = new Dog('汪汪', 2);

为什么要这么写呢?

我在前面说 this 的创建与返回是隐性的,但在工作中为了让构造过程更易可见与更易维护,所以才有了上述使用 that 代替 this,同时手动返回 that 的做法;

这也验证了隐性的这两步确实是存在的。


但上述这个解释我觉得不够完美,它只描述了构造器属性是如何塞给实例,没说原型上的属性是如何给实例继承的。


我在winter大神的重学前端专栏中,看到了比较符合我心意的,同时也是符合原理的描述:

  • 以构造器的 prototype 属性为原型,创建新对象;
  • 将 this (也就是上一句中的新对象)和调用参数传给构造器,执行;
  • 如果构造器没有手动返回对象,则返回第一步创建的对象;

到这里不管怎么说,你都应该大概知道了new过程中:会新建对象,此对象会继承构造器的原型与原型上的属性,最后它会被作为实例返回这样一个过程。知道了原理,我们来手动实现一个简单的new方法。

3. 实现一个简单的 new 方法

// 构造器函数
let Dog = function (name, age) {
  this.name = name;
  this.age = age;
};
Dog.prototype.sayName = function () {
  console.log(this.name);
};
// 定义的new方法
let newMethod = function (Dog, ...rest) {
  // 1. 以构造器的 prototype 属性为原型,创建新对象;
  let myDog = Object.create(Dog.prototype);
  // 2. 将this和调用参数传给构造器执行
  Dog.apply(myDog, rest);
  // 3. 返回第一步的对象
  return myDog;
};
// 创建实例,将构造函数 Dog 与形参作为参数传入
const myDog = newMethod(Dog, '汪汪', 2);
myDog.sayName() // '汪汪';
// 最后检验,与使用new的效果相同
console.log( myDog instanceof Dog ) // true
console.log( myDog.hasOwnProperty('name') ); // true
console.log( myDog.hasOwnProperty('age') ); // true
console.log( myDog.hasOwnProperty('sayName') );// false




相关文章
|
3月前
|
JavaScript 前端开发
如何在 JavaScript 中使用 __proto__ 实现对象的继承?
使用`__proto__`实现对象继承时需要注意原型链的完整性和属性方法的正确继承,避免出现意外的行为和错误。同时,在现代JavaScript中,也可以使用`class`和`extends`关键字来实现更简洁和直观的继承语法,但理解基于`__proto__`的继承方式对于深入理解JavaScript的面向对象编程和原型链机制仍然具有重要意义。
|
3月前
|
Web App开发 JavaScript 前端开发
如何确保 Math 对象的方法在不同的 JavaScript 环境中具有一致的精度?
【10月更文挑战第29天】通过遵循标准和最佳实践、采用固定精度计算、进行全面的测试与验证、避免隐式类型转换以及持续关注和更新等方法,可以在很大程度上确保Math对象的方法在不同的JavaScript环境中具有一致的精度,从而提高代码的可靠性和可移植性。
|
3月前
|
JSON 前端开发 JavaScript
JavaScript中对象的数据拷贝
本文介绍了JavaScript中对象数据拷贝的问题及解决方案。作者首先解释了对象赋值时地址共享导致的值同步变化现象,随后提供了五种解决方法:手动复制、`Object.assign`、扩展运算符、`JSON.stringify`与`JSON.parse`组合以及自定义深拷贝函数。每种方法都有其适用场景和局限性,文章最后鼓励读者关注作者以获取更多前端知识分享。
40 1
JavaScript中对象的数据拷贝
|
3月前
|
设计模式 JavaScript 前端开发
js中new和object.creat区别
【10月更文挑战第29天】`new` 关键字和 `Object.create()` 方法在创建对象的方式、原型链继承、属性初始化以及适用场景等方面都存在差异。在实际开发中,需要根据具体的需求和设计模式来选择合适的方法来创建对象。
|
3月前
|
JavaScript 前端开发 图形学
JavaScript 中 Math 对象常用方法
【10月更文挑战第29天】JavaScript中的Math对象提供了丰富多样的数学方法,涵盖了基本数学运算、幂运算、开方、随机数生成、极值获取以及三角函数等多个方面,为各种数学相关的计算和处理提供了强大的支持,是JavaScript编程中不可或缺的一部分。
|
4月前
|
存储 JavaScript 前端开发
JavaScript 对象的概念
JavaScript 对象的概念
67 4
|
4月前
|
存储 JavaScript 前端开发
js中函数、方法、对象的区别
js中函数、方法、对象的区别
48 2
|
4月前
|
缓存 JavaScript 前端开发
JavaScript中数组、对象等循环遍历的常用方法介绍(二)
JavaScript中数组、对象等循环遍历的常用方法介绍(二)
72 1
|
4月前
|
JavaScript 前端开发 Unix
Node.js 全局对象
10月更文挑战第5天
57 2
|
4月前
|
JavaScript 前端开发 大数据
在JavaScript中,Object.assign()方法或展开语法(...)来合并对象,Object.freeze()方法来冻结对象,防止对象被修改
在JavaScript中,Object.assign()方法或展开语法(...)来合并对象,Object.freeze()方法来冻结对象,防止对象被修改
90 0

热门文章

最新文章

  • 1
    当面试官再问我JS闭包时,我能答出来的都在这里了。
    40
  • 2
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    27
  • 3
    Node.js 中实现多任务下载的并发控制策略
    32
  • 4
    【2025优雅草开源计划进行中01】-针对web前端开发初学者使用-优雅草科技官网-纯静态页面html+css+JavaScript可直接下载使用-开源-首页为优雅草吴银满工程师原创-优雅草卓伊凡发布
    26
  • 5
    【JavaScript】深入理解 let、var 和 const
    49
  • 6
    【04】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架二次开发准备工作-以及建立初步后端目录菜单列-优雅草卓伊凡商业项目实战
    47
  • 7
    【03】Java+若依+vue.js技术栈实现钱包积分管理系统项目-若依框架搭建-服务端-后台管理-整体搭建-优雅草卓伊凡商业项目实战
    54
  • 8
    【02】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-ui设计图figmaUI设计准备-figma汉化插件-mysql数据库设计-优雅草卓伊凡商业项目实战
    57
  • 9
    如何通过pm2以cluster模式多进程部署next.js(包括docker下的部署)
    72
  • 10
    【01】Java+若依+vue.js技术栈实现钱包积分管理系统项目-商业级电玩城积分系统商业项目实战-需求改为思维导图-设计数据库-确定基础架构和设计-优雅草卓伊凡商业项目实战
    55