Javascript学习笔记(prototype对象)
来源:互联网 发布:tnt刷武器软件 编辑:程序博客网 时间:2024/05/17 03:59
prototype 对象
来自《JavaScript 标准参考教程(alpha)》,by 阮一峰
目录
- 概述
- 构造函数的缺点
- prototype 属性的作用
- 原型链
- constructor 属性
- instanceof 运算符
- Object.getPrototypeOf()
- Object.setPrototypeOf()
- Object.create()
- Object.prototype.isPrototypeOf()
- Object.prototype.__proto__
- 获取原型对象方法的比较
大部分面向对象的编程语言,都以“类”(class)为基础,实现对象的继承。JavaScript 语言不是如此,它的对象继承以“原型对象”(prototype)为基础。
概述
构造函数的缺点
JavaScript 通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部。
function Cat (name, color) { this.name = name; this.color = color;}var cat1 = new Cat('大毛', '白色');cat1.name // '大毛'cat1.color // '白色'
上面代码中,Cat
函数是一个构造函数,函数内部定义了name
属性和color
属性,所有实例对象(上例是cat1
)都会生成这两个属性,即这两个属性会定义在实例对象上面。
通过构造函数为实例对象定义属性,虽然很方便,但是有一个缺点。同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。
function Cat(name, color) { this.name = name; this.color = color; this.meow = function () { console.log('喵喵'); };}var cat1 = new Cat('大毛', '白色');var cat2 = new Cat('二毛', '黑色');cat1.meow === cat2.meow// false
上面代码中,cat1
和cat2
是同一个构造函数的两个实例,它们都具有meow
方法。由于meow
方法是生成在每个实例对象上面,所以两个实例就生成了两次。也就是说,每新建一个实例,就会新建一个meow
方法。这既没有必要,又浪费系统资源,因为所有meow
方法都是同样的行为,完全应该共享。
这个问题的解决方法,就是 JavaScript 的原型对象(prototype)。
prototype 属性的作用
JavaScript 的每个对象都继承另一个对象,后者称为“原型”(prototype)对象。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。null
也可以充当原型,区别在于它没有自己的原型对象。
JavaScript 继承机制的设计就是,原型的所有属性和方法,都能被子对象共享。
下面,先看怎么为对象指定原型。每一个构造函数都有一个prototype
属性,这个属性会在生成实例的时候,成为实例对象的原型对象。
function Animal(name) { this.name = name;}Animal.prototype.color = 'white';var cat1 = new Animal('大毛');var cat2 = new Animal('二毛');cat1.color // 'white'cat2.color // 'white'
上面代码中,构造函数Animal
的prototype
对象,就是实例对象cat1
和cat2
的原型对象。原型对象上添加一个color
属性,结果,实例对象都继承了该属性。
原型对象的属性不是实例对象自身的属性。只要修改原型对象,变动就立刻会体现在所有实例对象上。
Animal.prototype.color = 'yellow';cat1.color // "yellow"cat2.color // "yellow"
上面代码中,原型对象的color
属性的值变为yellow
,两个实例对象的color
属性立刻跟着变了。这是因为实例对象其实没有color
属性,都是读取原型对象的color
属性。也就是说,当实例对象本身没有某个属性或方法的时候,它会到构造函数的prototype
属性指向的对象,去寻找该属性或方法。这就是原型对象的特殊之处。
如果实例对象自身就有某个属性或方法,它就不会再去原型对象寻找这个属性或方法。
cat1.color = 'black';cat1.color // 'black'cat2.color // 'yellow'Animal.prototype.color // 'yellow';
上面代码中,实例对象cat1
的color
属性改为black
,就使得它不再去原型对象读取color
属性,后者的值依然为yellow
。
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
Animal.prototype.walk = function () { console.log(this.name + ' is walking');};
上面代码中,Animal.prototype
对象上面定义了一个walk
方法,这个方法将可以在所有Animal
实例对象上面调用。
构造函数就是普通的函数, 所以实际上所有函数都有prototype
属性。
原型链
对象的属性和方法,有可能定义在自身,也有可能定义在它的原型对象。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype chain)。比如,a
对象是b
对象的原型,b
对象是c
对象的原型,以此类推。
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype
,即Object
构造函数的prototype
属性。那么,Object.prototype
对象有没有它的原型呢?回答是有的,就是没有任何属性和方法的null
对象,而null
对象没有自己的原型。
Object.getPrototypeOf(Object.prototype)// null
上面代码表示,Object.prototype
对象的原型是null
,由于null
没有任何属性,所以原型链到此为止。
“原型链”的作用是,读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。如果直到最顶层的Object.prototype
还是找不到,则返回undefined
。
如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
需要注意的是,一级级向上,在原型链寻找某个属性,对性能是有影响的。所寻找的属性在越上层的原型对象,对性能的影响越大。如果寻找某个不存在的属性,将会遍历整个原型链。
举例来说,如果让某个函数的prototype
属性指向一个数组,就意味着该函数可以当作数组的构造函数,因为它生成的实例对象都可以通过prototype
属性调用数组方法。
var MyArray = function () {};MyArray.prototype = new Array();MyArray.prototype.constructor = MyArray;var mine = new MyArray();mine.push(1, 2, 3);mine.length // 3mine instanceof Array // true
上面代码中,mine
是构造函数MyArray
的实例对象,由于MyArray
的prototype
属性指向一个数组实例,使得mine
可以调用数组方法(这些方法定义在数组实例的prototype
对象上面)。至于最后那行instanceof
表达式,我们知道instanceof
运算符用来比较一个对象是否为某个构造函数的实例,最后一行就表示mine
为Array
的实例。
下面的代码可以找出,某个属性到底是原型链上哪个对象自身的属性。
function getDefiningObject(obj, propKey) { while (obj && !{}.hasOwnProperty.call(obj, propKey)) { obj = Object.getPrototypeOf(obj); } return obj;}
constructor 属性
prototype
对象有一个constructor
属性,默认指向prototype
对象所在的构造函数。
function P() {}P.prototype.constructor === P// true
由于constructor
属性定义在prototype
对象上面,意味着可以被所有实例对象继承。
function P() {}var p = new P();p.constructor// function P() {}p.constructor === P.prototype.constructor// truep.hasOwnProperty('constructor')// false
上面代码中,p
是构造函数P
的实例对象,但是p
自身没有contructor
属性,该属性其实是读取原型链上面的P.prototype.constructor
属性。
constructor
属性的作用,是分辨原型对象到底属于哪个构造函数。
function F() {};var f = new F();f.constructor === F // truef.constructor === RegExp // false
上面代码表示,使用constructor
属性,确定实例对象f
的构造函数是F
,而不是RegExp
。
有了constructor
属性,就可以从实例新建另一个实例。
function Constr() {}var x = new Constr();var y = new x.constructor();y instanceof Constr // true
上面代码中,x
是构造函数Constr
的实例,可以从x.constructor
间接调用构造函数。
这使得在实例方法中,调用自身的构造函数成为可能。
Constr.prototype.createCopy = function () { return new this.constructor();};
constructor
属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改constructor
属性,防止引用的时候出错。
function Person(name) { this.name = name;}Person.prototype.constructor === Person // truePerson.prototype.copy = function () { return new this.constructor(this.name);};
上面代码中,Person
构造函数的原型对象的contructor
属性指向Person
。然后,开发者在原型对象上定义了copy
方法,该方法内部通过this.constructor
调用Person
。如果原型对象变了,这个constructor
属性的指向可能就会出错。
Person.prototype = { method: function () {}};var p1 = new Person('张三');p1.copy() // TypeError: p1.copy is not a function
上面代码中,Person.prototype
改成另一个对象,但是忘了改写contructor
属性,结果导致调用实例方法报错。
所以,修改原型对象时,一般要同时校正constructor
属性的指向。
// 避免这种写法C.prototype = { method1: function (...) { ... }, // ...};// 较好的写法C.prototype = { constructor: C, method1: function (...) { ... }, // ...};// 更好好的写法C.prototype.method1 = function (...) { ... };
上面代码中,避免完全覆盖掉原来的prototype
属性,要么将constructor
属性重新指向原来的构造函数,要么只在原型对象上添加方法,这样可以保证instanceof
运算符不会失真。
此外,通过name
属性,可以从实例得到构造函数的名称。
function Foo() {}var f = new Foo();f.constructor.name // "Foo"
instanceof 运算符
instanceof
运算符返回一个布尔值,表示某个对象是否为指定的构造函数的实例。
var v = new Vehicle();v instanceof Vehicle // true
上面代码中,对象v
是构造函数Vehicle
的实例,所以返回true
。
instanceof
运算符的左边是实例对象,右边是构造函数。它会检查右边构建函数的原型对象(prototype),是否在左边对象的原型链上。因此,下面两种写法是等价的。
v instanceof Vehicle// 等同于Vehicle.prototype.isPrototypeOf(v)
由于instanceof
对整个原型链上的对象都有效,因此同一个实例对象,可能会对多个构造函数都返回true
。
var d = new Date();d instanceof Date // trued instanceof Object // true
上面代码中,d
同时是Date
和Object
的实例,因此对这两个构造函数都返回true
。
instanceof
的原理是检查原型链,对于那些不存在原型链的对象,就无法判断。
Object.create(null) instanceof Object // false
上面代码中,Object.create(null)
返回的新对象的原型是null
,即不存在原型,因此instanceof
就认为该对象不是Object
的实例。
除了上面这种继承null
的特殊情况,JavaScript 之中,只要是对象,就有对应的构造函数。因此,instanceof
运算符的一个用处,是判断值的类型。
var x = [1, 2, 3];var y = {};x instanceof Array // truey instanceof Object // true
上面代码中,instanceof
运算符判断,变量x
是数组,变量y
是对象。
注意,instanceof
运算符只能用于对象,不适用原始类型的值。
var s = 'hello';s instanceof String // false
上面代码中,字符串不是String
对象的实例(因为字符串不是对象),所以返回false
。
此外,对于undefined
和null
,instanceOf
运算符总是返回false
。
undefined instanceof Object // falsenull instanceof Object // false
利用instanceof
运算符,还可以巧妙地解决,调用构造函数时,忘了加new
命令的问题。
function Fubar (foo, bar) { if (this instanceof Fubar) { this._foo = foo; this._bar = bar; } else { return new Fubar(foo, bar); }}
上面代码使用instanceof
运算符,在函数体内部判断this
关键字是否为构造函数Fubar
的实例。如果不是,就表明忘了加new
命令。
Object.getPrototypeOf()
Object.getPrototypeOf
方法返回一个对象的原型。这是获取原型对象的标准方法。
// 空对象的原型是Object.prototypeObject.getPrototypeOf({}) === Object.prototype// true// 函数的原型是Function.prototypefunction f() {}Object.getPrototypeOf(f) === Function.prototype// true// f 为 F 的实例对象,则 f 的原型是 F.prototypevar f = new F();Object.getPrototypeOf(f) === F.prototype// true
Object.setPrototypeOf()
Object.setPrototypeOf
方法可以为现有对象设置原型,返回一个新对象。
Object.setPrototypeOf
方法接受两个参数,第一个是现有对象,第二个是原型对象。
var a = {x: 1};var b = Object.setPrototypeOf({}, a);// 等同于// var b = {__proto__: a};b.x // 1
上面代码中,b
对象是Object.setPrototypeOf
方法返回的一个新对象。该对象本身为空、原型为a
对象,所以b
对象可以拿到a
对象的所有属性和方法。b
对象本身并没有x
属性,但是 JavaScript 引擎找到它的原型对象a
,然后读取a
的x
属性。
new
命令通过构造函数新建实例对象,实质就是将实例对象的原型,指向构造函数的prototype
属性,然后在实例对象上执行构造函数。
var F = function () { this.foo = 'bar';};var f = new F();// 等同于var f = Object.setPrototypeOf({}, F.prototype);F.call(f);
Object.create()
生成实例对象的常用方法,就是使用new
命令,让构造函数返回一个实例。但是很多时候,只能拿到一个实例对象,它可能根本不是由构建函数生成的,那么能不能从一个实例对象,生成另一个实例对象呢?
JavaScript 提供了Object.create
方法,用来满足这种需求。该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。
// 原型对象var A = { print: function () { console.log('hello'); }};// 实例对象var B = Object.create(A);B.print() // helloB.print === A.print // true
上面代码中,Object.create
方法以A
对象为原型,生成了B
对象。B
继承了A
的所有属性和方法。这段代码等同于下面的代码。
var A = function () {};A.prototype = { print: function () { console.log('hello'); }};var B = new A();B.print === A.prototype.print // true
实际上,Object.create
方法可以用下面的代码代替。如果老式浏览器不支持Object.create
方法,可以就用这段代码自己部署。
if (typeof Object.create !== 'function') { Object.create = function (obj) { function F() {} F.prototype = obj; return new F(); };}
上面代码表明,Object.create
方法的实质是新建一个构造函数F
,然后让F.prototype
属性指向参数对象obj
,最后返回一个F
的实例,从而实现让该实例继承obj
的属性。
下面三种方式生成的新对象是等价的。
var obj1 = Object.create({});var obj2 = Object.create(Object.prototype);var obj3 = new Object();
如果想要生成一个不继承任何属性(比如没有toString
和valueOf
方法)的对象,可以将Object.create
的参数设为null
。
var obj = Object.create(null);obj.valueOf()// TypeError: Object [object Object] has no method 'valueOf'
上面代码中,对象obj
的原型是null
,它就不具备一些定义在Object.prototype
对象上面的属性,比如valueOf
方法。
使用Object.create
方法的时候,必须提供对象原型,即参数不能为空,或者不是对象,否则会报错。
Object.create()// TypeError: Object prototype may only be an Object or nullObject.create(123)// TypeError: Object prototype may only be an Object or null
object.create
方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,会立刻反映在新对象之上。
var obj1 = { p: 1 };var obj2 = Object.create(obj1);obj1.p = 2;obj2.p// 2
上面代码中,修改对象原型obj1
会影响到新生成的实例对象obj2
。
除了对象的原型,Object.create
方法还可以接受第二个参数。该参数是一个属性描述对象,它所描述的对象属性,会添加到实例对象,作为该对象自身的属性。
var obj = Object.create({}, { p1: { value: 123, enumerable: true, configurable: true, writable: true, }, p2: { value: 'abc', enumerable: true, configurable: true, writable: true, }});// 等同于var obj = Object.create({});obj.p1 = 123;obj.p2 = 'abc';
Object.create
方法生成的对象,继承了它的原型对象的构造函数。
function A() {}var a = new A();var b = Object.create(a);b.constructor === A // trueb instanceof A // true
上面代码中,b
对象的原型是a
对象,因此继承了a
对象的构造函数A
。
Object.prototype.isPrototypeOf()
对象实例的isPrototypeOf
方法,用来判断一个对象是否是另一个对象的原型。
var o1 = {};var o2 = Object.create(o1);var o3 = Object.create(o2);o2.isPrototypeOf(o3) // trueo1.isPrototypeOf(o3) // true
上面代码表明,只要某个对象处在原型链上,isPrototypeOf
都返回true
。
Object.prototype.isPrototypeOf({}) // trueObject.prototype.isPrototypeOf([]) // trueObject.prototype.isPrototypeOf(/xyz/) // trueObject.prototype.isPrototypeOf(Object.create(null)) // false
上面代码中,由于Object.prototype
处于原型链的最顶端,所以对各种实例都返回true
,只有继承null
的对象除外。
Object.prototype.__proto__
__proto__
属性(前后各两个下划线)可以改写某个对象的原型对象。
var obj = {};var p = {};obj.__proto__ = p;Object.getPrototypeOf(obj) === p // true
上面代码通过__proto__
属性,将p
对象设为obj
对象的原型。
根据语言标准,__proto__
属性只有浏览器才需要部署,其他环境可以没有这个属性,而且前后的两根下划线,表示它本质是一个内部属性,不应该对使用者暴露。因此,应该尽量少用这个属性,而是用Object.getPrototypeof()
(读取)和Object.setPrototypeOf()
(设置),进行原型对象的读写操作。
原型链可以用__proto__
很直观地表示。
var A = { name: '张三'};var B = { name: '李四'};var proto = { print: function () { console.log(this.name); }};A.__proto__ = proto;B.__proto__ = proto;A.print() // 张三B.print() // 李四
上面代码中,A
对象和B
对象的原型都是proto
对象,它们都共享proto
对象的print
方法。也就是说,A
和B
的print
方法,都是在调用proto
对象的print
方法。
A.print === B.print // trueA.print === proto.print // trueB.print === proto.print // true
可以使用Object.getPrototypeOf
方法,检查浏览器是否支持__proto__
属性,老式浏览器不支持这个属性。
Object.getPrototypeOf({ __proto__: null }) === null
上面代码将一个对象的__proto__
属性设为null
,然后使用Object.getPrototypeOf
方法获取这个对象的原型,判断是否等于null
。如果当前环境支持__proto__
属性,两者的比较结果应该是true
。
获取原型对象方法的比较
如前所述,__proto__
属性指向当前对象的原型对象,即构造函数的prototype
属性。
var obj = new Object();obj.__proto__ === Object.prototype// trueobj.__proto__ === obj.constructor.prototype// true
上面代码首先新建了一个对象obj
,它的__proto__
属性,指向构造函数(Object
或obj.constructor
)的prototype
属性。所以,两者比较以后,返回true
。
因此,获取实例对象obj
的原型对象,有三种方法。
obj.__proto__
obj.constructor.prototype
Object.getPrototypeOf(obj)
上面三种方法之中,前两种都不是很可靠。最新的ES6标准规定,__proto__
属性只有浏览器才需要部署,其他环境可以不部署。而obj.constructor.prototype
在手动改变原型对象时,可能会失效。
var P = function () {};var p = new P();var C = function () {};C.prototype = p;var c = new C();c.constructor.prototype === p // false
上面代码中,C
构造函数的原型对象被改成了p
,结果c.constructor.prototype
就失真了。所以,在改变原型对象时,一般要同时设置constructor
属性。
C.prototype = p;C.prototype.constructor = C;c.constructor.prototype === p // true
所以,推荐使用第三种Object.getPrototypeOf
方法,获取原型对象。
var o = new Object();Object.getPrototypeOf(o) === Object.prototype// true
- Javascript学习笔记(prototype对象)
- [前端JS学习笔记]JavaScript prototype 对象
- [前端JS学习笔记]JavaScript prototype 对象
- JavaScript prototype学习笔记
- javascript prototype 学习笔记
- Javascript学习笔记--理解prototype
- JavaScript学习笔记(4)---prototype
- javascript学习十一:面向对象之prototype
- JavaScript 学习笔记 三 原型(prototype)
- JavaScript学习笔记六:prototype的提出
- JavaScript的Prototype对象
- JavaScript [面向对象] prototype
- Javascript对象,prototype链
- JavaScript Prototype对象浅谈
- javaScript的prototype对象
- JavaScript原型对象prototype
- Javascript学习笔记-----对象
- javascript对象学习笔记
- 2017 人工智能+内容生产研究报告
- 工程院周济院长:关于中国智能制造发展战略的思考
- Eclipse中切换SVN已保存的用户名
- HBase基本数据操作详解【完整版,绝对精品】
- leetcode_668. Kth Smallest Number in Multiplication Table ? 待解决
- Javascript学习笔记(prototype对象)
- ORA-28000 帐户已被锁定的解决方法
- sublime前端插件推荐让sublime变成webstorm
- Qt Quick Controls模块
- 嵌入式C编程中的全局变量重复声明问题
- 设计模式【工厂方法模式】
- javascript 之模块化篇
- 非对称加密------RSA
- 你写的代码,是别人的噩梦吗?