目标:
- 了解 new 的实现原理,并手写一个new
- 掌握创建对象的多种方式,并了解它们的优缺点
- 掌握继承的多种方式,并了解它们的优缺点
1、new
进行 new
操作的时候干了些什么?
- 创建一个空对象,并且
this
变量引用该对象,同时还继承了该函数的模型 - 属性和方法被加入到
this
引用的对象中 - 新创建的对象由
this
所引用,并且最后隐式的返回this
因为 new
是 js
中的操作符,我们不能够像上面的 call2
和 apply2
一样创建新的 new
,这里我们用 newOp
来替代 new
function Person(name) {}
const person = new Person('relex')
// 使用newOp方法来替代new操作符,第一个参数就是要创建实例的构造函数,后面的参数就是调用构造函数时传递的参数。
const person = newOp(Person, 'relex')
function newOp() {
let obj = new Object()
// 第一个参数就是构造函数,shift掉之后arguments也会相应的去除第一个参数
let Constructor = Array.prototype.shift.call(arguments)
// 将obj的原型指向
obj.__proto__ = Constructor.prototype
Constructor.apply(obj, arguments)
return obj
}
构造函数又有以下这些情况:
- 返回的是一个对象
实例对象只能访问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
return
出来的对象的属性 - 返回的是一个基本类型
返回的是一个字符串这样的基本类型,与没有返回值的处理结果差不多,只有在构造函数中赋值了的才能够访问,所以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
new
还需要进一步改造,判断返回值类型是否是对象类型function newOp(context) { let obj = new Object() let Constructor = Array.prototype.shift.call(context) obj.__proto__ = Constructor.prototype var result = Constructor.apply(context, arguments) return typeof result == 'object' ? result : obj }
2、创建对象
2.1、工厂模式
function Person(name) {
let obj = new Obj()
obj.name = name
obj.getName = function() {
return this.name
}
return obj
}
优点:简单
缺点:所有的实例都指向一个原型,实例对象无法识别
用途:需要大量用到,通过工厂模式初始化好一个对象并返回供使用,例如UI组件的按钮、面板等
2.2、构造函数模式
function Person(name) {
this.name = name;
this.getName = getName //解决每次实例化对象时方法都要重新创建一次的问题
}
function getName() {
console.log(this.name)
}
var person1 = new Person('kevin');
优点:实例可以识别为一个特定的类型
2.3、原型模式
function Person() {}
Person.prototype = {
constructor: Person, //解决实例的原型对象constructor丢失的问题
name: 'relex',
getName: function() {
return this.name
}
}
var person1 = new Person();
缺点:所有的属性和方法都共享、不能初始化参数
2.4、构造函数和原型模式组合使用(推荐)
function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
};
var person1 = new Person();
优点:同时具备共享属性和私有属性
3、继承
3.1、原型链继承
function Parent () {
this.names = ['zhangsan'];
}
Parent.prototype.getName = function () {
console.log(this.name);
}
function Child () {}
Child.prototype = new Parent();
var child1 = new Child();
child1.names.push('lisi')
child1.getName() // ['zhangsan', 'lisi']
var child2 = new Child()
child2.getName() // ['zhangsan', lisi']
缺点:引用类型的属性被所有实例对象共享
3.2、构造函数继承
function Parent (age) {
this.names = ['zhangsan'];
this.age = age
}
function Child () {
Parent.call(this, age);
}
var child1 = new Child(24);
child1.names.push('lisi');
console.log(child1.names, child1.age); // ["zhangsan", "lisi"], 24
var child2 = new Child(25);
console.log(child2.names, child2.age); // ["zhangsan"], 25
优点:避免了引用类型的属性被所有实例对象共享的问题、可以在 Child 中向 Parent 传参
3.3、组合原型和构造函数继承(最常用)
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child('kevin', '18');
child1.colors.push('black');
console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
var child2 = new Child('daisy', '20');
console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]
优点:融合了原型链继承和构造函数继承的优点
3.4、原型继承
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}
var person = {
name: 'kevin',
friends: ['daisy', 'kelly']
}
var person1 = createObj(person);
var person2 = createObj(person);
person1.name = 'person1';
console.log(person2.name); // kevin
person1.friends.push('taylor');
console.log(person2.friends); // ["daisy", "kelly", "taylor"]
缺点:包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。
3.5、寄生继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
function createObj (o) {
var clone = Object.create(o);
clone.sayName = function () {
console.log('hi');
}
return clone;
}