海誉网站定制,wordpress iis 分页 404,企业工商信息查询系统官网,天津网站建设 熊掌号1.原型链
其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
简单回顾一下构造函数、原型和实例的关系#xff1a;每个构造函数都有一个原型对象#xff0c;原型对象都包含一个指向构造函数的指针#xff0c;而实例都包含一个指向原型对象的内部指针。… 1.原型链
其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。
简单回顾一下构造函数、原型和实例的关系每个构造函数都有一个原型对象原型对象都包含一个指向构造函数的指针而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例此时的原型对象将包含一个指向另一个原型的指针相应的另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例那么层层递进就构成了实例与原型的链条。这就是原型链的基本概念。
function Human(){this.type mammal
}
Human.prototype.getType function(){return this.type;
};function Person(){this.alive true;this.foods vegetable
};// 继承了Human
Person.prototype new Human()
// 给原型添加方法的代码放在替换原型的语句之后
Person.prototype.getAlive function(){return this.alive;
}
var instance new Person();instance.getType(); // mammal
console.log(instance.type); // mammal
instance.getAlive(); // true
console.log(instance.foods); // vegetable 上述代码实现的本质是用一个新类型的实例重写原型对象。
原来存在于Human实例中的所有属性和方法现在也存在于Person.prototype中。我们没有使用Person默认提供的原型而是给它换了一个新原型这个新原型就是Human的实例。
最终结果就是instance指向Person的原型Person的原型又指向Human的原型。getType()方法仍然还在Human.prototype中但type则位于Person.prototype中。这是因为type是一个实例属性而getType()则是一个原型方法。既然Person.prototype现在是Human的实例那么type当然就位于该实例中了。也就是说alive、foods存在于instance实例中type存在于Person.prototype实例中。因为彼此都是互相的实例属性。
现在instance.constructor现在指向的是Human这是因为原来Person.prototype被重写了的缘故。实际上不是Person原型的constructor属性被重写了而是Person的原型指向了另一个对象Human的原型而这个原型对象的constructor属性指向的是Human
通过实现原型链本质上扩展了原型搜索机制。即如果搜索一个实例属性时先搜索实例继续搜索实例的原型在通过原型链实现继承的情况下搜索过程就可以沿着原型链继续向上。调用instance.getType()会经历三个搜索步骤1搜索instance实例2搜索Person.prototype3搜索Human.prototype 搜索过程总是要一环一环地前行到原型链末端才会停下来。
如果上述代码Person原型上若重新定义getType()方法。那么调用instance.getType()则会读取Person原型上的这个方法屏蔽掉Human原型上的这个方法。但是Human的其他实例调用getType()方法则不受影响继续调用的是Human原型上的该方法。
function Human(){this.type mammal
}
Human.prototype.getType function(){return this.type;
};function Person(){this.alive true;this.foods vegetable
};// 继承了Human
Person.prototype new Human()
// 给原型添加方法的代码放在替换原型的语句之后
Person.prototype.getAlive function(){return this.alive;
};
Person.prototype.getType function(){return false;
};
const instance new Person();
instance.getType(); // false
const people new Human();
people.getType(); // mammal通过原型链实现继承的时候不能使用对象字面量创建原型方法。因为这样会重写原型链。 function Human(){this.type mammal
}
Human.prototype.getType function(){return this.type;
};function Person(){this.alive true;this.foods vegetable
};// 继承了Human
Person.prototype new Human()
// 给原型添加方法的代码放在替换原型的语句之后
Person.prototype {getAlive: function(){return this.alive;},getAnother: function(){return false;}
}
const instance new Person();
instance.getType(); // error
以上代码刚刚把Human的实例赋给Person原型紧接着又将原型替换成一个对象字面量。所以现在Person的原型包含的是一个Object的实例而不是Human的实例。原型链已经被切断。Person与Human之间已经没有关系了。
所有函数的默认原型都是Object的实例因此默认原型都会包含一个内部指针指向Object.prototype。这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因。也就是说Person继承了Human而Human继承了Object。当调用instance.toString()时实际上调用的是保存在Object.prototype中的那个方法。
由于原型链的关系instance是Object、Person、Human中任何一个类型的实例。
console.log(instance instanceof Object); // true
console.log(instance instanceof Person); // true
console.log(instance instanceof Human); // true
console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(Person.prototype.isPrototypeOf(instance)); // true
console.log(Human.prototype.isPrototypeOf(instance)); // true
原型链的问题
1尽管原型链很强大可以用它实现继承但它也存在一些问题。最主要的问题是包含引用类型值的原型。在通过原型实现继承时原型会变成另一个类型的实例。于是原先实例属性也就成为现在原型属性。如果原先实例属性是一个引用类型那么原型的其他实例都会共享这个属性。
function Human(){this.colors [white,black]
};
function Person(){
};
// Person继承了Human
Person.prototype new Human();const instance1 new Person();
instance1.colors.push(yellow);
console.log(instance1.colors); // [white,black, yellow]const instance2 new Person();
console.log(instance2.colors); // [white,black, yellow]
如上代码所有Person实例都共享了colors的属性。 2创建子类型的实例时不能向超类型的构造函数中传递参数。实际上应该说没有办法在不影响所有对象实例的情况下给超类型的构造函数传递参数。
2.借用构造函数
相对于原型而言借用构造函数有一个很大的优势即可以在子类型构造函数中向超类型构造函数传递参数。
function Human(name){this.name name;
};
Human.prototype.sayName function(){alert(this.name);
}
function Person(){// 继承了Human同时还传递了参数Human.call(this, lee);// 实例属性this.age 29;
};const instance new Person();
console.log(instance.name); // lee
console.log(instance.age); // 29
instance.sayName(); // error
为了确保Human里的属性不会重写Person中的实例属性所以在调用超类构造函数后再添加应该在子类中定义的属性。
借用构造函数的问题
1方法都在构造函数中定义函数复用无从谈起。
2在超类型的原型中定义的方法对子类型而言也时不可见的结果所有类型都只能使用构造函数模式。
考虑到这些问题借用构造函数的技术也时很少单独使用的。
3.组合继承
有时候也叫做伪经典继承指的是将原型链和借用构造函数的技术组合到一块从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承而通过借用构造函数来实现对实例属性的继承。这样既通过在原型上定义方法实现了函数复用又能够保证每个实例都有它自己的属性。
function Human(name){this.name name;this.colors [black,white];
};
Human.prototype.sayName function(){alert(this.name);
}
function Person(name, age){// 继承了Human同时还传递了参数Human.call(this, name); // 第二次调用超类型Human()// 实例属性this.age age;
};
Person.prototype new Human(); // 第一次调用超类型Human()
Person.prototype.constructor Person;
Person.prototype.sayAge function(){alert(this.age)
}
const instance new Person(lee, 29);
instance.colors.push(yellow);
console.log(instance.colors); // [black,white,yellow]
instance.sayName(); // lee
instance.sayAge(); // 29const instance1 new Person(Alice, 24);
console.log(instance1.colors); // [black,white]
instance1.sayName(); // Alice
instance1.sayAge(); // 24
组合继承的问题
无论什么情况下都会调用2次超类型构造函数一次是在创建子类型原型的时候另一次是在子类型构造函数内部。 如上面例子在第一次调用Human构造函数时Person.prototype会得到2个属性name和colors它们都是Human的实例属性只不过现在位于Person.prototype的原型中。当调用Person构造函数时又会调用一次Human构造函数这一次又在新对象上创建了实例属性name和colors。于是这两个属性就屏蔽了原型中的两个同名属性。也就是说有2组name和colors属性。一组在实例上一组在原型中。
组合继承避免了原型链和借用构造函数的缺陷融合了它们的优点成为javascript中最常用的继承模式。而且instanceof和isPrototypeOf也能够用于识别基于组合继承创建的对象。
4.原型式继承
借助原型可以基于已有的对象创建新对象同时还不必因此创建自定义类型。
function object(o){function F(){}F.prototype o;return new F();
}
从本质上讲object()对传入其中的对象执行了一次浅复制。
const person {name: lee,friends: [Alice, Bob, Jack]
}
// 这里的object是上面创建的那个object的函数
const anotherPerson object(person);
anotherPerson.name Gred;
anotherPerson.friends.push(Linda);const yetAnotherPerson object(person);
yetAnotherPerson.name Coco;
yetAnotherPerson.friends.push(lucy);console.log(person.friends); // [Alice, Bob, Jack, Linda, lucy]
console.log(anotherPerson .friends); // [Alice, Bob, Jack, Linda, lucy]
console.log(yetAnotherPerson.friends); // [Alice, Bob, Jack, Linda, lucy]
上面例子person中存在一个基本类型的属性和一个引用类型的属性。这意味着person.friends不仅属于person所有而且也会被anotherPerson以及yetAnotherPerson共享。实际上这就相当于又创建了person对象的两个副本。
ECMAScript5通过新增了object.create()方法规范了原型式继承。这个方法接收2个参数一个用作新对象原型的对象(可选的)和一个为新对象定义额外属性的对象。在传入一个参数的情况下object.create()和上面定义的object()方法的行为相同。
const person {name: lee,friends: [Alice, Bob, Jack]
}
// 这里的object是上面创建的那个object的函数
const anotherPerson object.create(person, {name: Gred
});
console.log(anotherPerson.name); // Gred
原型式继承的问题
与使用原型链实现继承有相似的问题。也就是包含引用类型的属性会共享相应的值。但是如果只想让一个对象与另一个对象保持类似的情况没必要创建构造函数。原型式继承式完全可以胜任。
5.寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似即创建一个仅用于封装继承过程的函数该函数在内部以某种方式来增强对象最后返回对象。
function createAnother(origin){const clone object(origin); // 通过调用函数创建一个新对象clone.sayHi function(){ // 以某种方式增强对象alert(Hi);};return clone; // 返回这个对象
}const person {name: lee,friends: [Alice, Bob, Jack]
}const anotherPerson createAnother(person);
anotherPerson.sayHi(); // Hi
anotherPerson.friends.push(Lee);
console.log(anotherPerson.friends); // [Alice, Bob, Jack, Lee]
console.log(person.friends); // [Alice, Bob, Jack, Lee]
寄生式继承的问题
与构造函数模式类似会因为不能做到函数复用而降低效率。上面例子object()函数不是必须的任何能够返回新对象的函数都适用于此模式。在主要考虑对象而不是自定义类型和构造函数的情况下寄生式继承也式一种有用的模式。
6.寄生组合式继承 所谓寄生组合式继承即通过借用构造函数来继承属性通过原型链的混合形式来继承方法。其基本思路式不必为了指定子类型的原型而调用超类型的构造函数我们所需的无非就是超类型原型的一个副本而已。本质上就是使用寄生式继承来继承超类型的原型然后再将结果指定给子类型的原型。
function inheritPrototype(person, human){const prototype object(human.prototype); // 创建对象副本prototype.constructor person; // 增强对象person.prototype prototype; // 指定对象
}
在函数内部第一步式创建超类型原型的一个副本。第二步式为创建的副本添加constructor属性从而弥补因重写原型而失去的默认constructor属性。最后一步将新创建的对象即副本赋值给子类型的原型。
function Human(name){this.name name;this.colors [black,white];
};
Human.prototype.sayName function(){alert(this.name);
}
function Person(name, age){// 继承了Human同时还传递了参数Human.call(this, name);// 实例属性this.age age;
};
inheritPrototype(Person, Human);
Person.prototype.sayAge function(){alert(this.age)
}
这个例子的高效体现在它只调用了一次Human构造函数并且因此避免了在Person.prototype上面创建不必要的、多余的属性。与此同时原型链还能保持不变因此还能够正常使用instanceof和isPrototypeOf()。开发人员普遍认为寄生组合式继承式引用类型最理想的继承范式。