发表于: 2017-05-05 21:23:34
1 1087
【js-04】原型链和访问原型的办法
小课堂【郑州第100期】
分享人:董瑞
1.背景介绍
继承:继承是OO语言(面对对象语言)的一个重要概念,许多OO语言支持两种继承方式:接口继承和实现继承。
接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,在ECMAScript中无法实现接口继承,只支持实现继承。 并且其实现继承主要是依靠原型链实现的。--JavaScript高级程序设计
2.知识剖析
2.1原型prototype JavaScript的每个对象都继承另一个对象,后者称为“原型”(prototype)对象。只有null除外,它没有自己的原型对象。
原型对象上的所有属性和方法,都能被派生对象共享。这就是JavaScript继承机制的基本设计。 通过构造函数生成实例对象时,会自动为实例对象分配原型对象。每一个构造函数都有一个prototype属性,这个属性就是实例对象的原型对象。
//创建构造函数Animal
function Animal(name) {
this.name = name;
}
// 创建两个实例对象
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');
//构造函数Animal的prototype对象,就是实例对象cat1和cat2的原型对象。
// 在原型对象上添加一个color属性。结果,实例对象都能读取该属性。
Animal.prototype.color = 'white';
console.log(cat1.color);// 'white'
console.log(cat1.color); // 'white'
//原型对象的属性不是实例对象自身的属性。
// 但只要修改原型对象,变动就立刻会体现在所有实例对象上。
Animal.prototype.color = 'yellow';
console.log(cat1.color); // "yellow"
console.log(cat1.color); // "yellow"
//这是因为实例对象其实没有color属性,都是读取原型对象的color属性。
// 也就是说,当实例对象本身没有某个属性或方法的时候,
// 它会到构造函数的prototype属性指向的对象,去寻找该属性或方法。
// 这就是原型对象的特殊之处。
//如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
cat1.color = 'black';
Animal.prototype.color = 'yellow';
console.log(cat1.color);// black
console.log(cat2.color);// "yellow";
//上面代码中,实例对象cat1的color属性改为black
// 就使得它不再去原型对象读取color属性,但cat2的值依然为yellow。
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的含义,而实例对象可以视作从原型对象衍生出来的子对象。JS中所有对象都有自己的原型对象
2.2 原型链 对象的属性和方法,有可能是定义在自身,也有可能是定义在它的原型对象。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype chain)。
比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。 “原型链”的作用是,读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype还是找不到,则返回undefined。
2.3constructor属性 prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。 constructor属性的作用,是分辨原型对象到底属于哪个构造函数。
//demo2 原型链
//2.1
function P() {};
P.prototype.constructor === P;
//定义了一个函数P,P的原型的构造全等于P
//2.2
//创建实例函数小p
var p = new P();
p.constructor;
// function P() {}
//小p的contructor属性全等于大P的contructor属性
p.constructor === P.prototype.constructor;// true
//但是小P自身没有contructor属性,
// 该属性其实是读取原型链上面的P.prototype.constructor属性。
p.hasOwnProperty('constructor');// false
//2.3
//constructor属性的作用,是分辨原型对象到底属于哪个构造函数
//新建一个构造函数大F,创建实例对象小f
function F() {};
function G() {};
var f = new F();
//实例对象小f的构造函数是大F,而不是G
f.constructor === F ;// true
f.constructor === G ;// false
3.常见问题
访问对象原型的方法有哪些?
4.解决方法
获取实例对象obj的原型对象,有三种方法
- obj.__proto__
- obj.constructor.prototype
- Object.getPrototypeOf(obj)
上面三种方法之中,前两种都不是很可靠。
最新的ES6标准规定,__proto__属性只有浏览器才需要部署,其他环境可以不部署。而obj.constructor.prototype在手动改变原型对象时,可能会失效。
5.编码实战
//demo3 获取原型对象方法的比较
//3.1
function H() {};
var h = new H();
// 三种方法都能获取到当前对象的原型对象
h.__proto__===H.prototype;// true
h.constructor.prototype===H.prototype;// true
Object.getPrototypeOf(h)===H.prototype;// true
//注意:最新的ES6标准规定,__proto__属性只有浏览器才需要部署,
// 其他环境可以不部署,所以不推荐使用这种方法。
//而obj.constructor.prototype在手动改变原型对象时,可能会失效。
//见下方demo
////3.2
var M = function () {};
var m = new M();
var N = function () {};
N.prototype = m;
//N构造函数的原型对象被改成了m
//N.prototype.constructor = N;
//在改变原型对象时,一般要同时设置constructor属性才能成功。
var n = new N();
n.constructor.prototype === m ;// false
//导致了n.constructor.prototype失真
//综上,推荐使用第三种Object.getPrototypeOf方法,获取原型对象。
6.扩展思考
instanceof运算符的原型链原理? instanceof运算符返回一个布尔值,表示指定对象是否为某个构造函数的实例。
var v = new Vehicle();
v instanceof Vehicle // true
instanceof运算符的左边是实例对象,右边是构造函数。它的运算实质是检查右边构建函数的原型对象,是否在左边对象的原型链上。 由于instanceof对整个原型链上的对象都有效,因此同一个实例对象,可能会对多个构造函数都返回true。
7.参考文献
参考一:阮一峰
参考二:接口继承
参考三:并没有实现继承
参考四:原型对象
参考五:派生对象
参考六:实例对象
参考七:构造函数
参考八:面向对象语言
参考九:constructor属性
8.更多讨论
从原型对象生成新的实例对象除了new命令外有没有其他方法?
评论