详解new()

什么是new()

在 JavaScript 中,new 关键字是用于创建对象实例的操作符。当你在 JavaScript 中使用 new 关键字时,它会执行以下操作:

  1. 创建一个空对象。
  2. 将这个空对象的原型链接到构造函数的原型对象上。
  3. 将构造函数的作用域赋给这个新对象(因此 this 关键字指向这个新对象)。
  4. 执行构造函数中的代码(初始化新对象的属性等)。
  5. 如果构造函数没有明确返回一个对象,则返回新创建的对象实例。

下面是一个简单的 JavaScript 示例,演示了如何使用 new 关键字创建对象实例:

js 复制代码
// 构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 使用 new 关键字创建 Person 的实例
var person1 = new Person('Alice', 30);

// person1 现在是 Person 的一个实例
console.log(person1.name); // 输出: Alice
console.log(person1.age);  // 输出: 30

在这个示例中,new Person('Alice', 30) 创建了一个名为 person1 的新对象实例,这个对象具有 nameage 属性,值分别为 'Alice'30

为什么要有new?

在 JavaScript 中,new 关键字的主要作用是创建对象实例。它的存在有几个重要原因:

  1. 实例化对象: 使用 new 关键字可以根据构造函数创建新的对象实例。这允许我们在 JavaScript 中使用面向对象的编程范式,即通过创建对象实例来表示和操作数据。
  2. 继承原型链: new 关键字会确保新创建的对象实例能够正确继承构造函数的原型链。这意味着新对象可以访问构造函数原型对象上定义的方法和属性。
  3. 确保正确的 this 上下文: 使用 new 关键字会确保在构造函数内部使用的 this 关键字指向新创建的对象实例,而不是全局对象。这使得构造函数能够正确地初始化新对象的属性。
  4. 明确标识对象实例: 在 JavaScript 中,new 关键字的存在使得我们能够清晰地区分构造函数和普通函数。通过使用 new 关键字调用构造函数,我们明确地表明正在创建一个新的对象实例。

总的来说,new 关键字的存在使得在 JavaScript 中进行面向对象编程更加方便和清晰,能够创建并正确初始化对象实例,确保原型链继承,并且提供了明确的方式来标识对象实例的创建过程。

如何手写一个new?

new 运算符创建⼀个⽤户定义的对象类型的实例或具有构造函数的内置对象类型之⼀。

先看看 new 实现了哪些功能。

js 复制代码
function Person (name, age) {
    this.name = name;
    this.age = age;
    this.habit = 'Games';
}

Person.prototype.strength = 80;
Person.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
}

var person = new Person('Kevin', '18');
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60
person.sayYourName(); // I am Kevin

我们可以看到,实例 person 可以:

  1. 访问到 Otaku 构造函数⾥的属性;
  2. 访问到 Otaku.prototype 中的属性;

接下来,我们可以尝试着模拟⼀下了。 因为 new 是关键字,所以⽆法像 bind 函数⼀样直接覆盖,所以我们写⼀个函数,命名为 objectFactory,来模拟 new 的效果。⽤的时候是这样的:

js 复制代码
function Person () {
    ......
}

// 使⽤ new
var person = new Person(......);
// 使⽤ objectFactory
var person = objectFactory(Person, ......)

1. 初步实现

因为 new 的结果是⼀个新对象,所以在模拟实现的时候,我们也要建⽴⼀个新对象,假设这个对象叫obj,因为 obj 会具有 Person 构造函数⾥的属性,我们可以使⽤ Person.apply(obj, arguments) 来给 obj 添加新的属性。

然后,实例的 proto 属性会指向构造函数的 prototype,也正是因为建⽴起这样的关系,实例可以访问原型上的属性

js 复制代码
// 第⼀版代码
function objectFactory() {
    var obj = new Object();
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    Constructor.apply(obj, arguments);
    return obj;
};

在这⼀版中,我们:

  1. ⽤new Object() 的⽅式新建了⼀个对象 obj;
  2. 取出第⼀个参数,就是我们要传⼊的构造函数。此外因为 shift 会修改原数组,所以 arguments 会被去除第⼀个参数;
  3. 将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性;
  4. 使⽤ apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性;
  5. 返回 obj;

测试下:

js 复制代码
function Person (name, age) {
    this.name = name;
    this.age = age;
    this.habit = 'Games';
}

Person.prototype.strength = 60;
Person.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
}

function objectFactory() {
    var obj = new Object(),
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    Constructor.apply(obj, arguments);
    return obj;
};

var person = objectFactory(Person, 'Kevin', '18')
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60
person.sayYourName(); // I am Kevin

2. 最终实现

假如构造函数有返回值

js 复制代码
function Person (name, age) {
    this.strength = 60;
    this.age = age;
    return {
        name: name,
        habit: 'Games'
    }
}

var person = new Person('Kevin', '18');
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // undefined
console.log(person.age) // undefined

在这个例⼦中,构造函数返回了⼀个对象,在实例 person 中只能访问返回的对象中的属性。 ⽽且还要注意⼀点,在这⾥我们是返回了⼀个对象,假如我们只是返回⼀个基本类型的值呢? 再举个例⼦:

js 复制代码
function Person (name, age) {
    this.strength = 60;
    this.age = age;
    return 'handsome boy';
}

var person = new Otaku('Kevin', '18');
console.log(person.name) // undefined
console.log(person.habit) // undefined
console.log(person.strength) // 60
console.log(person.age) // 18

这次尽管有返回值,但是相当于没有返回值进⾏处理。

所以我们还需要判断返回的值是不是⼀个对象,如果是⼀个对象,我们就返回这个对象,如果没有,我 们该返回什么就返回什么。

最终版的代码

js 复制代码
function objectFactory() {
    // 创建一个空对象
    var obj = new Object();
    // 从参数中取出构造函数,并将其从参数列表中删除
    Constructor = [].shift.call(arguments);
    // 将新对象的原型链接到构造函数的原型对象上
    obj.__proto__ = Constructor.prototype;
    // 调用构造函数,并将新对象作为上下文
    var ret = Constructor.apply(obj, arguments);
    // 如果构造函数返回了一个对象,则返回该对象;否则返回新创建的对象
    return typeof ret === 'object' ? ret : obj;
};
相关推荐
真滴book理喻1 小时前
Vue(四)
前端·javascript·vue.js
程序员_三木1 小时前
Three.js入门-Raycaster鼠标拾取详解与应用
开发语言·javascript·计算机外设·webgl·three.js
开心工作室_kaic3 小时前
springboot476基于vue篮球联盟管理系统(论文+源码)_kaic
前端·javascript·vue.js
川石教育3 小时前
Vue前端开发-缓存优化
前端·javascript·vue.js·缓存·前端框架·vue·数据缓存
搏博3 小时前
使用Vue创建前后端分离项目的过程(前端部分)
前端·javascript·vue.js
温轻舟3 小时前
前端开发 之 12个鼠标交互特效上【附完整源码】
开发语言·前端·javascript·css·html·交互·温轻舟
web135085886353 小时前
2024-05-18 前端模块化开发——ESModule模块化
开发语言·前端·javascript
LCG元4 小时前
javascript页面设计案例【使用HTML、CSS和JavaScript创建一个基本的互动网页】
javascript
技术程序猿华锋4 小时前
Gemini 2.0 Flash 体验版实测:日常视觉识别的最佳选择,关键在于其API Key现在是免费调用
开发语言·javascript·ecmascript·googlecloud·gemini
TttHhhYy5 小时前
uniapp+vue开发app,蓝牙连接,蓝牙接收文件保存到手机特定文件夹,从手机特定目录(可自定义),读取文件内容,这篇首先说如何读取,手机目录如何寻找
开发语言·前端·javascript·vue.js·uni-app