prototype
我们知道每个函数都有一个prototype指向他的原型对象。
我们可以这么理解: 每个对象在创建时就与另一个对象产生关联,其实就是对另一个对象的引用。而js 再找对象属性时会顺着原型链一直往上找,直到无法找到为止
来看一下
function Person(name) {
this.name = name
}
// 控制台打印一下, => 是结果
Person.prototype => { constructor: f }
// 而 Person.prototype.constructor 又指向与构造函数自己
Person.prototype.constructor == Person // true
用下图表示:
proto
每个对象都有一个__proto__
属性,除了null外,而这个属性又指向他的原型对象
验证一下
function Person(name) {
this.name = name
}
var person = new Person("Bill");
// 控制台打印一下, person.__proto__
person.__proto__ => { constructor: f }
// 我们从上面[prototype]看 Person.prototype 也是指向 { constructor: f }, 那么他们是否是同一个东西呢?
person.__proto__ == Person.prototype // 结果是 true 的
// 上面一样的话,那么
person.__proto__.contructor == Person // true
用图表示一下就是
new 操作符
这里解释一下new 操作符,其实跟原型有关
首先搞清楚new
做了什么
- 创建对象
- 将对象的原型指向函数的原型
- 改变
this
上下文 - 返回这个对象
function newObj(fn, ...args) {
var obj = {};
obj.__proto__ = fn.prototype;
let res = fn.call(obj, ...args);
let isObject = typeof res === 'object' && typeof res !== null;
let isFunction = typeof res === 'function';
return isObject || isFunction ? res : obj;
// 这里其实可以直接返回obj
// 但防止 fn 这个方法直接返回this, 以下这种情况
// function fn(name) {
// this.name = name;
// return this;
// }
}
function Person(name) {
this.name = name;
}
// 测试一下
person = newObj(Person, "Bill")
// 打印一下 person
// Person {name: Bill}
Fn.prototype.proto
- 我们说Fn.prototype 是一个原型对象,我们上面说只要是对象都有
__proto__
, 那么Fn.prototype.__proto
指向的是什么呢?
答案是: Object.prototype
Object.prototype == Person.prototype.__proto__ // true
所以有了下图
到此原型基本已经讲完了
继承
下面写继承
原型链继承
function Parent(){
this.role = "parent"
this.list = [1,2,3]
}
Parent.prototype.getRole = function() {
return this.role
}
function Child(name) {
this.name = name
}
Child.prototype = new Parent();
Child.prototype.getName = function() {
return this.name;
}
var children = new Child("admin");
children.getName()
children.getRole()
var parent = new Parent();
console.log(parent.role) // parent
Child.prototype.value = "123"
console.log(parent.value); // "undefined"
console.log(children.value) // “123”
// 以下是原型链的缺点
var children2 = new Child("child2");
console.log(children.list, children2.list) // [1,2,3], [1,2,3,4]
children.list.push(4);
console.log(children.list, children2.list) // [1,2,3,4], [1,2,3,4]
看下图
- 当执行了
Child.prototype = new Parent()
, 我们说找属性的话,会顺着原型链去找, 比如children
要找getRole
,那么他会一直找,Child.prototype.__proto__
下有getRole
, 这就是继承 - 当执行了
Child.prototype.value = "123"
,children
实例也拥有了value 。 - 缺点:
- 只要在某个子类下改变了父类的引用类型的值,那么子类都会改变,这样做就不能做到相互独立了
- 无法实现父类构造函数属性的赋值
使用call,改变this 作用域
function Parent() {
this.role = "parent"
}
function Child(role, name) {
Parent.call(this, role)
this.name = name;
}
var children = new Child("children", "哈哈哈");
// 注意
Parent.prototype.getRole = function() {
return this.role;
}
// 上面原型方法,子类是无法继承的,即children没有getRole 方法
优点:
- 解决了原型继承无法向父类赋值的问题
- 可以继承多个构造函数属性(call多个);
缺点:
- 只能继承父类构造函数的属性。原型链新创建的无法被继承
- 无法实现构造函数的复用。(每次用每次都要重新调用)
- 每个新实例都有父类构造函数的副本,臃肿。
组合模式
将call方式和原型链方式组合起来
function Parent(role) {
this.role = role
}
Parent.prototype.value = "test";
function Child(role, name) {
Parent.call(this, role);
this.name = name;
}
Child.prototype = new Parent();
var children = new Child("child", "admin")
优点:
- 可以继承父类原型上的属性,可以传参,可复用;
- 每个新实例引入的构造函数属性是私有的;
缺点:
- 调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数;
寄生式继承
function Parent(role) {
this.role = role;
this.list = [1,2,3];
}
Parent.prototype.value = "123"
// 原型式继承
function _extend(obj) {
function F(){};
F.prototype = obj; // F.prototype = new Parent() -> 还是回到原型链继承
return new F(); // 这里像不像 原型链继承上面的 new Child();
}
var parent = new Parent(); // 获取父类实例
// 以上是原型式继承,以下给原型继承在套个壳子传递参数
function wrap(obj, name, role){
var child = _extend(obj);
child.role = role;
child.name = name;
return child;
}
var child = wrap(parent, 'test', "child");
console.log(child.role) // "test"
console.log(child.value) // "123"
var child1 = wrap(parent, "Bill", "child1");
console.log(child1.list) // [1,2,3]
child1.list.push(456);
console.log(child1.list) // [1,2,3,456]
console.log(child.list) // [1,2,3,456]
看上面
- 又回到了原型链继承的缺点之一了,对于父类引用类型的,一旦做了修改,子类的都会发生改变
寄生组合式继承
function Parent(role) {
this.role = role;
this.list = [1,2,3];
}
Parent.prototype.value = "123"
function Child(name, role) {
Parent.call(this, role) // 等于有了父类构造函数的副本
this.name = name;
}
function _extend(obj) {
function F(){};
F.prototype = obj; // F.prototype = new Parent() -> 还是回到原型链继承
return new F(); // 这里像不像 原型链继承上面的 new Child();
}
var extendFn = _extend(Parent.prototype);
Child.prototype = extendFn;
extendFn.constructor = Child;
var children = new Child("test", "child");
我们看下图
上面与下面其实大致一样,
function F(){};
F.prototype = Parent.prototype;
function Child() {
Parent.call(this);
}
Child.prototype = new F(); // 是不是有点像组合模式
// 寄生组合模式理想版
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);//实现继承
SubType.prototype.sayAge = function(){
alert(this.age);
}