JS中的原型和原型链的指向,上一篇已经说过了,这里有必要在画一个简图继续的关注一下。
new 的实现
根据上图可以简单的说:new 关键字实现把一个变量的__proto__指向函数的原型的prototype。如下
var foo; foo.__proto__=Foo.prototype
事实并非如此,new 关键字做了一下四个步骤
- 一个继承自
Foo.prototype
的新对象 foo 被创建; - foo.proto 指向 Foo.prototype,foo.__proto = Foo.prototype;
- 把 this 指向新的对象 foo;
- 返回新对象,这里需要注意:
- 如果构造函数没有返回值,则返回this;
- 如果构造函数返回的是基本类型,如
number
、string
、boolean
;则返回this - 如果构造函数返回一个对象类型,则返回这个对象
根据如上四个步骤实现一下new 关键字
// 我们通过一个函数newFunc实现new关键字,根据使用定义函数
// 如何使用
const foo=newFunc(Foo);
function newFunc(){
// 创建一个新对象
const obj = new Object();
// 取出第一个参数,即 Foo 令 obj.__proto__ = Foo.prototype;
const [Constructor,...rest] = arguments;
obj.__proto__ = Constructor.prototype;
// 改变this指向
const result = Constructor.apply(obj,rest);
// 返回新的对象
return typeof result === 'object'?result:obj
}
反过头分析一下为什么 var foo; foo.__proto__=Foo.prototype
这种方式不行呢?我们只把_ protp_ 的指向问题解决,并没有改变 this的指向,此时this指向foo。
继承
继承 是面向对象编程中很重要的一个概念,es6 有了class 和 extends 关键字 可以很方便的实现继承,在此之前都是通过原型进行继承,实现继承有如下四个方法。
1. 原型链继承
function Parent(){
this.name = "Lius"
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(){}
// 继承,通俗来讲是子类可以使用父类的属性和方法,所以把 new Parent() 赋值给 Child.prototype;
Child.prototype = new Parent();
// 我们把Child.prototype = new Parent();后,因为Child.prototype.constructor=Child,通过上面的定义我们重写了Child的constructor
// 为了保证正确,我们要将Child.prototype.constructor=Child
Child.prototype.constructor=Child;
const child = new Child();
child.getName()
存在问题
- 如果属性是引用类型,那么改变一个实例的值就会把所有实例都改变
function Parent(){
this.name = ['Lius1','Lius2']
}
Parent.prototype.getName = function(){
console.log(this.name)
}
function Child(){}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child1 = new Child();
const child2 = new Child();
child1.name.pop();
child1.getName(); // ['Lius1']
child2.getName(); // ['Lius1']
// 因为child1 和 child2 的this相同, __proto__ 都指向同一个地址
- 实例化Child的时候,不能够传参
只能通过Parent 传参,不能通过 Child 传参;
2. 构造函数继承
每一种继承方式的实现都是为了解决上一种继承方式存在的问题,所以构造函数继承是为了解决原型链继承存在的两个问题
- 属性是引用类型
function Parent(){
this.name = ['Lius1','Lius2']
}
// 上面也说了出现问题的原因,所以通过Child的构造函数改变this的指向,让每个实例有自己单独的this
// 通过 call 实现
function Child(){
Parent.call(this)
}
const child1 = new Child();
const child2 = new Child();
child1.name.pop();
console.log(child1.name); // ['Lius1']
console.log(child2.name); // ['Lius1','Lius2']
- 传参问题
function Parent(name,age){
this.name = name;
this.age = age;
}
// 上面也说了出现问题的原因,所以通过Child的构造函数改变this的指向,让每个实例有自己单独的this
// 通过 call 实现, 通过ext区分出 parent 参数
function Child(id,ext=[]){
Parent.call(this,...ext);
this.id = id
}
const child1 = new Child(1,[['Lius'],18]);
const child2 = new Child(2,[['Lius1','Lius2'],19]);
child1.name.pop()
console.log(child1.name,child1.age) // [], 18
console.log(child2.name,child2.age) // ['Lius1','Lius2'],19
** 存在问题** 父类只能在构造函数中定义属性和方法,但是方法在构造函数中定义,每次创建实例都会创建一次方法,浪费内存
function Parent(name,age){
this.name = name;
this.age = age;
this.log = function(){
console.log(this.name)
}
}
function Child(id,ext=[]){
Parent.call(this,...ext);
this.id = id
}
const child1 = new Child(1,[['Lius'],18]);
const child2 = new Child(2,[['Lius1','Lius2'],19]);
console.log(child1.log === child2.log) // false
3. 组合继承
把上面👆两种继承方式组合一下,属性定义在构造函数中,方法定义在prototype中
function Parent(name,age){
this.name = name;
this.age = age;
}
Parent.prototype.log = function(){
console.log(this.name)
}
function Child(id,ext=[]){
Parent.call(this,...ext);
this.id = id
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child1 = new Child(1,[['Lius'],18]);
const child2 = new Child(2,[['Lius1','Lius2'],19]);
console.log(child1.log === child2.log) // true
存在问题 Parent 调用了两次,做了重复操作
- Parent.call(this,...ext);
- Child.prototype = new Parent();
4. 寄生组合式继承
这次解决的是 Parent 重复调用问题
function Parent(name,age){
this.name = name;
this.age = age;
}
Parent.prototype.log = function(){
console.log(this.name)
}
function Child(id,ext=[]){
Parent.call(this,...ext);
this.id = id
}
const Temp = function(){}
// 为什么不直接 Child.prototype = Parent.prototype ?
// 因为在后续我们给 Child.prototype 添加新的方法和属性的时候 Parent.prototype
// 也会跟着改变,类似于浅拷贝
Temp.prototype = Parent.prototype
Child.prototype = new Temp();
Child.prototype.constructor = Child;
const child1 = new Child(1,[['Lius'],18]);
const child2 = new Child(2,[['Lius1','Lius2'],19]);
console.log(child1.log === child2.log) // true