构造函数用于初始化实例对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const p1 = { name: '张三', age: 20, run: function () { console.log('调用此方法张三就开始奔跑'); }, };
const p2 = { name: '李四', age: 22, run: function () { console.log('调用此方法李四就开始奔跑'); }, };
const p3 = { name: '王五', age: 21, run: function () { console.log('调用此方法王五就开始奔跑'); }, };
|
这种场景很常见,每个对象都有name
、age
属性和run
方法,我们可以用一个函数封装一下。
封装
封装的目的是为了减少重复代码的编写。
提取共性,将不同点作为参数传入。可以如下封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function CreatePerson(name, age) { this.name = name; this.age = age; this.run = function () { console.log(`调用此方法${this.name}就会开始奔跑`); }; }
const p1 = new CreatePerson('张三', 20); const p2 = new CreatePerson('李四', 30); const p3 = new CreatePerson('王五', 40); p1.run(); p2.run(); p3.run();
|
这种方式有个问题,就是每次使用 CreatePerson 函数创建对象时,run 方法被反复创建了多次。同样的内容,占据了多份内存空间。
原型对象
属于所有实例共享的属性和方法,抽离出来放在原型对象中,而每个实例特有的属性和方法,都会留在构造函数中。
例如,每一个实例的名字,名字是每个实例特有的,不可能被共享。
将 run 方法挂载到原型对象上,这样就可以实现一次创建,被多个实例对象共同使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function CreatePerson(name, age) { this.name = name; this.age = age; }
CreatePerson.prototype.run = function () { console.log(`调用此方法${this.name}就会开始奔跑`); };
const p1 = new CreatePerson('张三', 20); const p2 = new CreatePerson('李四', 30); const p3 = new CreatePerson('王五', 40); p1.run(); p2.run(); p3.run();
|
new 关键字都干了什么?
- 创建一个新对象,该对象为最终返回的实例
- 将新对象的原型对象指向构造函数的原型对象
- 将构造函数内部的 this 指向这个新对象,即为实例
- 如果构造函数明确返回了对象或函数,那么这个返回值取代第一部的新对象
实现一个 new
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function myNew(Fn, ...args) { let res = {};
if (Fn.prototype !== null) { Object.setPrototypeOf(res, Fn.prototype); }
ret = Fn.apply(res, args);
if ((typeof ret === 'Object' || typeof ret === 'function') && ret !== null) { return ret; }
return res; }
const p1 = myNew(CreatePerson, '张三', 20); const p2 = myNew(CreatePerson, '李四', 30); const p3 = myNew(CreatePerson, '王五', 40); p1.run(); p2.run(); p3.run();
|
小结:构造函数的返回值为基本类型,其返回值是实例化后的对象。构造函数的返回值为引用类型,其返回值即为 new 之后的返回值。
构造函数和原型对象的访问优先级
如果在构造函数和原型对象中,同时声明了属性/方法,那么会优先访问构造函数中的属性/方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function CreatePerson(name, age) { this.name = name; this.age = age; this.run = function () { console.log(`构造函数中的:调用此方法${this.name}就会开始奔跑`); }; }
CreatePerson.prototype.run = function () { console.log(`调用此方法${this.name}就会开始奔跑`); };
const p1 = new CreatePerson('张三', 20); const p2 = new CreatePerson('李四', 30); const p3 = new CreatePerson('王五', 40); p1.run(); p2.run(); p3.run();
|