继承与原型链
//1.基于类的语言有Java/C++,但JS是基于原型的,JS是动态的,本身不提供class的实现。虽在 ES2015/ES6中引入class关键字,但只是语法糖,JS仍是基于原型的。
//2.继承------JS只有一种结构:对象。(重点重点重点)
//每个实例对象(object)都有一个私有属性(称之为 __proto__)指向它的构造函数的原型对象(**prototype**)。
//该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为null。
//根据定义,null没有原型,并作为这个**原型链**中的最后一个环节。
//3.几乎所有JS中的对象都是位于原型链顶端的Object的实例。
//4.原型继承通常被认为是JS的弱点之一,但原型继承模型本身实际比经典模型更强大。如在原型模型的基础上构建经典模型相当简单。
1. 基于原型链的继承
1. 继承属性
//JS对象是动态的属性“包”(指其自己的属性)。
//JS对象有一个指向一个原型对象的链。
//当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
//遵循ECMAScript标准,someObject.[[Prototype]]符号是用于指向someObject的原型。
//从ECMAScript6开始,[[Prototype]]可以通过Object.getPrototypeOf()和Object.setPrototypeOf()访问器来访问。
//这个等同于JS的非标准但许多浏览器实现的属性__proto__。
//但它不应该与构造函数func的prototype属性相混淆。
//被构造函数创建的实例对象的[[Prototype]]指向func的prototype属性。Object.prototype属性表示Object的原型对象。
//这里演示当尝试访问属性时会发生什么:
//让我们从一个函数里创建一个对象o,它自身拥有属性a和b的:
let f = function () {
this.a = 1;
this.b = 2;
}
/* 这么写也一样
function f() {
this.a = 1;
this.b = 2;
}
*/
let o = new f(); // {a: 1, b: 2}
//在 f 函数的原型上定义属性
f.prototype.b = 3;
f.prototype.c = 4;
// 不要在 f 函数的原型上直接定义 f.prototype = {b:3,c:4};这样会直接打破原型链
// o.[[Prototype]] 有属性 b 和 c
// (其实就是 o.__proto__ 或者 o.constructor.prototype)
// o.[[Prototype]].[[Prototype]] 是 Object.prototype.
// 最后 o.[[Prototype]].[[Prototype]].[[Prototype]] 是 null
// 这就是原型链的末尾,即 null,
// 根据定义,null 就是没有 [[Prototype]]。
// 综上,整个原型链如下:
// {a:1, b:2}
console.log(o.a); // 1
// a 是 o 的自身属性吗?是的,该属性的值为 1
console.log(o.b); // 2
// b 是 o 的自身属性吗?是的,该属性的值为 2
// 原型上也有一个'b'属性,但是它不会被访问到。
// 这种情况被称为"属性遮蔽 (property shadowing)"
console.log(o.c); // 4
// c 是 o 的自身属性吗?不是,那看看它的原型上有没有
// c 是 o.[[Prototype]] 的属性吗?是的,该属性的值为 4
console.log(o.d); // undefined
// d 是 o 的自身属性吗?不是,那看看它的原型上有没有
// d 是 o.[[Prototype]] 的属性吗?不是,那看看它的原型上有没有
// o.[[Prototype]].[[Prototype]] 为 null,停止搜索
// 找不到 d 属性,返回 undefined
//代码来源链接:<https://repl.it/@khaled_hossain_code/prototype>
//给对象设置属性会创建自有属性。获取和设置行为规则的唯一例外是当继承的属性带有getter或setter时。
2. 继承方法
var o = {
a: 2,
m: function(){
return this.a + 1;
}
};
console.log(o.m());
var p = Object.create(o);
p.a = 4;
console.log(p.m());
3. 在JS中使用原型
//在JS,函数(function)是允许拥有属性的。
//所有的函数会有一个特别的属性 —— `prototype` 。
//强烈建议打开浏览器的控制台(在 Chrome 和火狐浏览器中,按 Ctrl+Shift+I或F12),进入“console”选项卡,把如下的JS代码复制粘贴到窗口中,最后通过按下回车键运行代码。
function doSomething(){}
console.log( doSomething.prototype );
// 和声明函数的方式无关,
// JS中的函数永远有一个默认原型属性。
var doSomething = function(){};
console.log( doSomething.prototype );
//结果如下:
{
constructor: ƒ(),
[[Prototype]]: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
//我们可以给 doSomething 函数的原型对象添加新属性,如下:
function doSomething(){}
doSomething.prototype.foo = "bar";
console.log( doSomething.prototype );
//结果如下:
{
foo: "bar",
constructor: ƒ(),
[[Prototype]]: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
//通过 new 操作符来创建基于这个原型对象的 doSomething 实例。
//使用 new 操作符,只需在调用 doSomething 函数语句之前添加 new。
//便可以获得这个函数的一个实例对象。一些属性就可以添加到该原型对象中。
function doSomething(){}
doSomething.prototype.foo = "bar"; // add a property onto the prototype
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value"; // add a property onto the object
console.log( doSomeInstancing );
//运行的结果:
doSomething {
prop: "some value",
[[Prototype]]: {
foo: "bar",
constructor: ƒ doSomething(),
[[Prototype]]: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
}
//doSomeInstancing中的[[Prototype]]是doSomething.prototype.
//但这是做什么的呢?当你访问doSomeInstancing中的一个属性,浏览器首先会查看doSomeInstancing中是否存在这个属性。
//doSomeInstancing不包含属性信息,那么浏览器会在doSomeInstancing的[[Prototype]]中进行查找 (同 doSomething.prototype). 如属性在doSomeInstancing的[[Prototype]]中查找到,则使用doSomeInstancing中[[Prototype]]的属性。
//否则,如果doSomeInstancing中[[Prototype]]不具有该属性,则检查doSomeInstancing的[[Prototype]]的[[Prototype]]是否具有该属性。
//默认情况下,任何函数的原型属性[[Prototype]]都是window.Object.prototype.
//通过doSomeInstancing的[[Prototype]]的[[Prototype]]( 同 doSomething.prototype 的[[Prototype]](同Object.prototype)) 来查找要搜索的属性。
//如果属性不存在doSomeInstancing的[[Prototype]]的[[Prototype]]中,那么就会在doSomeInstancing的[[Prototype]]的[[Prototype]]的[[Prototype]]中查找。
//然而,这里存在个问题:doSomeInstancing的[[Prototype]]的[[Prototype]]的[[Prototype]]其实不存在。
//因此,只有这样,在[[Prototype]]的整个原型链被查看之后,这里没有更多的[[Prototype]],浏览器断言该属性不存在,并给出属性值为undefined的结论。
function doSomething(){}
doSomething.prototype.foo = "bar";
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value";
console.log("doSomeInstancing.prop: " + doSomeInstancing.prop);
console.log("doSomeInstancing.foo: " + doSomeInstancing.foo);
console.log("doSomething.prop: " + doSomething.prop);
console.log("doSomething.foo: " + doSomething.foo);
console.log("doSomething.prototype.prop: " + doSomething.prototype.prop);
console.log("doSomething.prototype.foo: " + doSomething.prototype.foo);
//结果如下:
doSomeInstancing.prop: some value
doSomeInstancing.foo: bar
doSomething.prop: undefined
doSomething.foo: undefined
doSomething.prototype.prop: undefined
doSomething.prototype.foo: bar
4. 使用不同的方法来创建对象和生成原型链
1. 使用语法结构创建的对象
var o = {a: 1};
var a = ["yo", "whadup", "?"];
function f(){
return 2;
}
2. 使用构造器创建的对象
function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v){
this.vertices.push(v);
}
};
var g = new Graph();
console.log(g)
Graph {
edges: []
vertices: []
[[Prototype]]: {
addVertex: ƒ (v)
[[Prototype]]:{
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
}
3. 使用 Object.create 创建的对象
var a = {a: 1};
var b = Object.create(a);
console.log(b.a);
var c = Object.create(b);
var d = Object.create(null);
console.log(d.hasOwnProperty);
4. 使用 class 关键字创建的对象
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}
var square = new Square(2);
console.log(square)
结果如下:
5. 性能
function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v){
this.vertices.push(v);
}
};
var g = new Graph();
console.log(g.hasOwnProperty('vertices'));
console.log(g.hasOwnProperty('nope'));
console.log(g.hasOwnProperty('addVertex'));
console.log(g.Prototype.hasOwnProperty('addVertex'));
const object1 = {
a: 'somestring',
b: 42,
c: false
};
console.log(Object.keys(object1)); /Array ["a", "b", "c"]
6. 错误实践:扩展原生对象的原型
5. prototype 和 Object.getPrototypeOf
//JS完全是动态的,都是运行时,而且不存在类(class)。
//所有的都是实例(对象)。即使我们模拟出的“类”,也只是一个函数对象。
//function A有一个叫做prototype的特殊属性。该特殊属性可与JS的new操作符一起使用。
//对原型对象的引用被复制到新实例的内部[[Prototype]]属性。
//如当执行var a1 = new A();时,JS(在内存中创建对象之后,和在运行函数A()把this指向对象之前)设置a1.[[Prototype]] = A.prototype;。
//然后当您访问实例的属性时,JS首先会检查它们是否直接存在于该对象上,如果不存在,则会[[Prototype]]中查找。
//这意味着你在prototype中定义的所有内容都可以由所有实例有效地共享,你甚至可以稍后更改部分prototype,并在所有现有实例中显示更改(如果有必要的话)。
//像上面的例子中,如果你执行var a1 = new A(); var a2 = new A();
//那么a1.doSomething事实上会指向Object.getPrototypeOf(a1).doSomething,它就是你在A.prototype.doSomething中定义的内容。
//也就是说:Object.getPrototypeOf(a1).doSomething == Object.getPrototypeOf(a2).doSomething == A.prototype.doSomething
//(补充:实际上,执行a1.doSomething()相当于执行Object.getPrototypeOf(a1).doSomething.call(a1)==A.prototype.doSomething.call(a1))
//简而言之,prototype是用于类的,而Object.getPrototypeOf()是用于实例的(instances),两者功能一致。
//[[Prototype]]看起来就像递归引用,
//如a1.doSomething、Object.getPrototypeOf(a1).doSomething、Object.getPrototypeOf(Object.getPrototypeOf(a1)).doSomething等等,直到它被找到或Object.getPrototypeOf返回null。
//因此,当你执行:
var o = new Foo();
//JavaScript 实际上执行的是:
var o = new Object();
o.__proto__ = Foo.prototype;
Foo.call(o);
//(或者类似上面这样的),然后,当你执行:
o.someProp;
//它检查 o 是否具有someProp属性。
//如果没有,它会查找Object.getPrototypeOf(o).someProp,
//如果仍旧没有,它会继续查找Object.getPrototypeOf(Object.getPrototypeOf(o)).someProp。
6. 结论
7. 示例
function A(a){
this.varA = a;
}
A.prototype = {
varA : null,
doSomething : function(){
}
}
function B(a, b){
A.call(this, a);
this.varB = b;
}
B.prototype = Object.create(A.prototype, {
varB : {
value: null,
enumerable: true,
configurable: true,
writable: true
},
doSomething : {
value: function(){
A.prototype.doSomething.apply(this, arguments);
},
enumerable: true,
configurable: true,
writable: true
}
});
B.prototype.constructor = B;
var b = new B();
b.doSomething();