推荐好文:# 你还不知道 常用 ES6 语法糖?!
前言
JavaScirpt中new关键字,是JavaScript的通配符,作用于构造函数,用于创建一个实例化对象。在面向对象编程,继承机制等都能找到new 关键字的身影。下文,对new 底层实现机制进行剖析,再手写一个new功能相同的函数,加深对new 的理解。
明白new 底层
1. 创建一个新的对象
function Person(name) {
this.name = name;
}
const awei = new Person('awei辣');
const bwei = new Person('awei辣');
console.log(awei);
console.log(bwei == awei);
- 当我们使用
new关键字调用Person 构造函数时,JavaScript 创建了一个新的对象。 - 提问:明明两个传入相同属性值的wei对象,
bwei == awei为什么输出false?- 因为
==比较对象地址,因此,我们了解到bewi对象并不是调用awei在栈内存里面的引用,而是在堆内存创建拥有name='awei辣'属性的新对象。
- 因为
2.绑定this
new关键字不仅创建新对象,还将该对象的内部 [[Prototype]] 链接到了 Person.prototype。这使得新对象能够继承来自 Person.prototype 的所有属性和方法。
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function () {
console.log(this.name);
}
const awei = new Person('张三');
const bwei = new Person('李四');
console.log(awei.name); // 张三 调用属性
bwei.sayName() // 李四 调用新增方法
``
构造函数this绑定到新创建的对象,并且可以在构造函数内部对新对象进行属性和方法添加,例如`Person.prototype.sayName `
- 3.设置原型链
```js
function Person(name) {
this.name = name;
}
const awei = new Person('张三');
console.log(awei.name); // 张三 调用属性
3.返回处理值
前三项完成后,new将创建的对象返回wei,使得指向该栈内存的新对象引用。
了解完new 底层进行的四项操作后,下面写个函数实现一样的操作。
多种方式——手撕 new关键字
先总结使用 new 操作符 底层的机制 :
- 创建新对象:JavaScript 引擎首先创建了一个全新的空对象
{}。- 设置原型链:接着,该新对象的内部属性
[[Prototype]](可通过__proto__访问)被设置为Person.prototype,建立了新对象与构造函数之间的原型链连接。- 绑定
this:然后,构造函数内的this被绑定到了新创建的对象上,使得我们可以在构造函数内部对新对象进行属性和方法的添加。- 返回新对象:最后,如果构造函数没有显式返回另一个对象,则默认返回新创建的对象。
1,字面量与 Object.create 实现
const personProto = { greet() { return "hello"; } };
const person = Object.create(personProto);
person.name = "Jane";
console.log(person, person.__proto__);
在这里,Object.create 通过原型 personProto 创建了一个新对象,并保持原型链的继承关系。
2,实现源码如下(简单版objectFactory函数)
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(this.name);
}
// 实现一个objectFactory 函数,创建一个对象,注意:不能使用new 关键字
function objectFactory(Fun,...args) {
let obj = {};
obj.__proto__ = Fun.prototype;
Fun.apply(obj,args);
return obj;
}
let awei = objectFactory(Person,'张三',18);
console.log(awei.name);
awei.sayName();
3,深入 es6实现(推荐)
function myNewES6(Constructor, ...args) {
const obj = Object.create(Constructor.prototype); // 创建对象,并设置原型
const result = Constructor.apply(obj, args); // 执行构造函数
return result instanceof Object ? result : obj; // 返回构造结果
}
这里使用ECMAScript里面Object.create 来设置__proto__,实现比较好的兼容和性能。同时,也使用剩余参数语法(...args)传入数组属性作为参数。
4,(es5实现)原生方式
面试时候,只要让面试官知道会写es5 就行,首要推荐写es6 形式,当然还有下面Object.assign 实现方式。
function myNew() {
var obj = {}; // 创建一个空对象
var Constructor = [].shift.call(arguments); // 取出构造函数
obj.__proto__ = Constructor.prototype; // 继承原型链
// 执行构造函数,将 `obj` 作为 `this`
var result = Constructor.apply(obj, arguments);
return typeof result === "object" && result !== null ? result : obj;
}
new关键字会创建一个新对象,并自动将__proto__指向构造函数的prototype。- 通过
apply绑定this,让构造函数执行并返回新对象。 - 如果构造函数返回的是对象类型(非
null),那么返回该对象,否则返回obj本身。
5,Object.assign 合并对象,实现new
const target = { a: 1, b: 2 };
const source = { b: 4, c: { d: 5 } };
const res = Object.assign({}, target, source); // 避免修改 target
console.log(res === target); // false,res 是新对象
console.log(res, res.__proto__); // { a: 1, b: 4, c: { d: 5 } },覆盖 `b`
console.log(target); // { a: 1, b: 2 }
console.log(source); // { b: 4, c: { d: 5 } }
Object.assign进行对象合并,避免直接修改target。- 浅拷贝,嵌套对象
c仍然是引用。
看下面图片,说明我们的手写new关键字成了。sayName 方法都可以调用。
复现 Object.create
- 创建一个新对象,并设置其
__proto__为proto。 - 支持属性定义,可使用
Object.defineProperties添加属性。 - 处理特殊情况:
proto不是对象或null时抛出错误。proto为null时,确保__proto__为空。
Object.create = function (proto, propertyObject = undefined) {
if (typeof proto !== "object" && typeof proto !== "function") {
throw new TypeError("Object prototype may only be an Object or null: " + proto);
}
if (propertyObject === null) {
throw new TypeError("Cannot convert undefined or null to object");
}
function F() {}
F.prototype = proto;
const obj = new F();
if (propertyObject !== undefined) {
Object.defineProperties(obj, propertyObject);
}
if (proto === null) {
obj.__proto__ = null;
}
return obj;
};
关键点:
- 构造函数
F继承proto,避免直接操作__proto__。 - 动态添加属性,用
Object.defineProperties控制属性特性(可写、可枚举等)。 - 兼容
proto === null的情况。
补充: Object.defineProperties 设置属性特性
let obj = {};
Object.defineProperties(obj, {
a: {
value: 1,
writable: true,
enumerable: true,
configurable: true
},
b: {
value: 2,
writable: true,
enumerable: true,
configurable: true
}
});
console.log(obj); // { a: 1, b: 2 }
writable:是否可修改。enumerable:是否可遍历。configurable:是否可删除或重新配置。
总结:
new的核心是创建对象、设置原型、执行构造函数并返回。Object.create可精确控制原型继承,并支持null原型对象。Object.assign进行浅拷贝,注意引用类型问题。