javascript中实现一个自己的new

简述


传统的 javascript 中只有对象,没有类的概念。它是基于原型的面向对象语言。原型对象特点就是将自身的属性共享给新对象。
先看一下 new 关键字是他都做了什么,让我们通过 new 实现继承的。
下面请看代码:

1
2
3
4
5
6
7
8
9
10
11
12
function Car(name, year, model) {
this.name = name;
this.year = year;
this.model = model;
}
Car.prototype.getV = function () {
return this.name + '--' + this.year + '--' + this.model;
};
var carOne = new Car('jeep', '2018', 'Wrangler');
console.log(carOne.getV());
console.log(carOne);
<font color='#ff502c'></font>;

new 关键字进行如下的操作:

  1. 创建一个空的 JavaScript 对象;
  2. 链接该对象(即设置该对象的构造函数)到另一个对象;
  3. 将步骤 1 新创建的对象作为 this 的上下文;
  4. 如果该函数没有返回对象,则返回 this。

同时,我们自己写的这个函数接收的第一个参数就是我们要继承的对象。
下面我们就一步一步实现一个自己的 new 关键字

第一步

第一步比较简单我们要首先定义一个 create 方法在方法内创建一个空对象

1
2
3
4
function create() {
// 创建一个空对象
let obj = new Object();
}

这个对象会在后面用到,经过后面的处理,如果没有返回值,就会返回我们创建的这个空对象

第二步

第二步比较关键,用到了我们基于 prototype 继承的知识。就是把我们新创建的这个空对象proto,指向我们要继承对象prototype
下面我们就在第一步代码的基础上实现

1
2
3
4
5
6
7
8
function create() {
// 创建一个空对象
let obj = new Object();
// 获取第一个参数,构造函数
let Preson = [].shfit.call(arguments);
// 链接该对象(即设置该对象的构造函数)到另一个对象;
obj.__proto__ = Preson.prototype;
}

在这一步我们就是通过proto关联了我们创建的空对象的 prototype 到我们要继承的另一个对象。

第三步

第三步,将步骤 1 新创建的对象作为this 的上下文,我们通过apply 执行构造函数并且改变 this 指向。
下面我们在第二步的基础上实现

1
2
3
4
5
6
7
8
9
10
function create() {
// 创建一个空对象
let obj = new Object();
// 获取第一个参数,构造函数
let Preson = [].shfit.call(arguments);
// 链接该对象(即设置该对象的构造函数)到另一个对象;
obj.__proto__ = Preson.prototype;
// 绑定this指向,执行构造函数
let result = Preson.apply(obj, arguments);
}

第四步

判断是否有返回值,如果该函数没有返回对象,则返回 this

1
2
3
4
5
6
7
8
9
10
11
12
function create() {
// 创建一个空对象
let obj = new Object();
// 获取第一个参数,构造函数
let Preson = [].shfit.call(arguments);
// 链接该对象(即设置该对象的构造函数)到另一个对象;
obj.__proto__ = Preson.prototype;
// 绑定this指向,执行构造函数
let result = Preson.apply(obj, arguments);

return typeof result === 'object' ? result : obj;
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function create() {
// 创建一个空对象
let obj = new Object();
// 获取第一个参数,构造函数
let Preson = [].shift.call(arguments);
// 链接该对象(即设置该对象的构造函数)到另一个对象;
obj.__proto__ = Preson.prototype;
// 绑定this指向,执行构造函数
let result = Preson.apply(obj, arguments);

return typeof result === 'object' ? result : obj;
}
function Car(name, year, model) {
this.name = name;
this.year = year;
this.model = model;
}
var carOne = create(Car, 'jeep', '2018', 'Wrangler');
var carTwo = new Car('jeep', '2018', 'Wrangler');
console.log(carOne);
console.log(carTwo);
console.log(carOne.__proto__ === Car.prototype); // true
console.log(carTwo.__proto__ === Car.prototype); // true

一道面试题
一道有关 new 的面试题,其实一部还是考察运算符优先级。

1
2
3
4
5
6
7
8
9
10
11
function Foo() {
return this;
}
Foo.getName = function () {
console.log(1);
};
Foo.prototype.getName = function () {
console.log(2);
};
new Foo.getName();
new Foo().getName();

其实这个是考察运算优先级和 new 的面试题,通过 mdn 上的运算优先级
new Foo() 的优先级大于 new Foo ,所以对于上述代码来说可以这样划分执行顺序

1
2
new (Foo.getName())();
new Foo().getName();

对于第一个函数来说,先执行了 Foo.getName() ,所以结果为 1;对于后者来说,先执行 new Foo() 产生了一个实例,然后通过原型链找到了 Foo 上的 getName 函数,所以结果为 2。

mdn new 运算符
大佬 new 的实现