第3章原型
· 使用 prototype 原型对象解决构造函数的问题
· 理解什么是原型(原型对象)
· 构造函数、prototype 原型对象、实例对象三者之间的关系
· 实例对象读写原型对象
· 属性成员搜索原则:原型链
· 原型对象的简写形式
· 原生对象的原型
· 原型对象的问题及使用建议
3.1 构造函数的 prototype属性
JavaScript 的每个对象都继承另一个父级对象,父级对象称为原型(prototype)对象。
原型也是一个对象,原型对象上的所有属性和方法,都能被子对象 (派生对象) 共享通过构造函数生成实例对象时,会自动为实例对象分配原型对象。而每一个构造函数都有一个prototype属性,这个属性就是实例对象的原型对象。
null没有自己的原型对象。
这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在构造函数的prototype属性上,
也就是实例对象的原型对象上。
function Cat(color) {
this.color=color;
}
Cat.prototype.name="猫";
Cat.prototype.sayhello=function(){
console.log('hello'+this.name,this.color);
}
Cat.prototype.saycolor=function (){
console.log('hello'+this.color);
}
var cat1=newCat('白色');
var cat2=newCat('黑色');
cat1.sayhello();
cat2.saycolor();
这时所有实例的name属性和sayhello()、saycolor方法,其实都是同一个内存地址,指向构造函数的prototype属性,因此就提高了运行效率节省了内存空间。
3.2 构造函数、实例、原型三者之间的关系
构造函数的prototyp属性,就是由这个构造函数new出来的所有实例对象的原型对象
每个对象都有一个constructor属性,该属性指向创建该实例的构造函数
对象.__proto__ (两边都是两个下划线):获取对象的原型对象;
console.log(cat1.__proto__==Cat.prototype); // true
注意:ES6标准规定,__proto__属性只有浏览器才需要部署,其他环境可以不部署,因此不建议使用
3.3 原型对象的获取及修改
上节可以看到,想要获取一个实例对象的原型对象,有两种方式:
1:通过实例对象的构造函数的prototype属性获取: 实例对象.constructor.prototype
2:通过实例对象的 __proto__ 属性获取: 实例对象.__proto__
而这两种方式,我们都不建议使用:
obj.constructor.prototype在手动改变原型对象时,可能会失效。
function P() {};
var p1=newP();
function C() {};
// 修改构造函数的prototype属性的值为p1
C.prototype=p1; //也就是说,此后所有有C构造函数得到的对象的原型对象都是p1;
var c1=newC();
console.log(c1.constructor.prototype===p1) // false
推举设置获取实例对象的原型的方式:
Object.getPrototypeOf(实例对象) 方法返回一个对象的原型对象。
这是获取原型对象的标准方法。
function Cat(name, color) {
this.name=name;
}
var cat1=newCat('猫'); //获取cat1对象的原型对象
var s=Object.getPrototypeOf(cat1);
console.log(s);
Object.setPrototypeOf(实例对象,原型对象) 为现有对象设置原型对象第一个是实例对象,第二个是要设置成为实例对象的原型对象的对象这是设置原型对象的标准方法。
function Cat(name) {
this.name=name;
}
var ob= {p:'波斯'};
var cat1=newCat('猫');
//设置cat1的原型对象为ob
Object.setPrototypeOf(cat1,ob);
console.log(cat1.p);//cat1的原型对象中有p属性
console.log(Object.getPrototypeOf(cat1));
console.log(cat1.__proto__);
//注意:如果对象的原型被改变,不会影响构造函数获取的原型的结果
console.log(Cat.prototype==cat1.__proto__); //false
以上的两中方法,都是在ES6新标准中添加的;
3.4 原型及原型链
所有对象都有原型对象;
function Cat(name, color) {
this.name = name;
}
var cat1 = new Cat('猫');
console.log(cat1.__proto__.__proto__.__proto__);
而原型对象中的属性和方法,都可以被实例对象直接使用;
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性
· 搜索首先从对象实例本身开始
· 如果在实例中找到了具有给定名字的属性,则返回该属性的值
· 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性
· 如果在原型对象中找到了这个属性,则返回该属性的值
· 如果还是找不到,就到原型的原型去找,依次类推。
· 如果直到最顶层的Object.prototype还是找不到,则返回undefined。
而这正是多个对象实例共享原型所保存的属性和方法的基本原理。
对象的属性和方法,有可能是定义在自身内,也有可能是定义在它的原型对象上。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototypechain)。
注意,不在要原型上形成多层链式查找,非常浪费资源
3.5 更简单的原型语法
我们注意到,前面例子中每添加一个属性和方法就要敲一遍构造函数.prototype。为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:
function Person (name, age) {
this.name=name
this.age=age
}
Person.prototype= {
type: 'human',
sayHello: function () {
console.log('我叫'+this.name+',我今年'+this.age+'岁了')
}
}
在该示例中,将Person.prototype重置到了一个新的对象。这样做的好处就是为Person.prototype添加成员简单了,但是也会带来一个问题,那就是原型对象丢失了constructor成员(构造函数)。
所以,为了保持constructor的指向正确,建议的写法是:
functionPerson (name, age) {
this.name=name
this.age=age
}
Person.prototype= {
// 将这个对象的构造函数指向Person
//constructor:Person, // => 手动将 constructor 指向正确的构造函数
type: 'human',
sayHello: function () {
console.log('我叫'+this.name+',我今年'+this.age+'岁了')
}
}
var p=newPerson();
3.6 原生对象的原型
所有构造函数都有prototype属性;
· Object.prototype
· Function.prototype
· Array.prototype
· String.prototype
· Number.prototype
· Date.prototype
· ……
为内置对象扩展原型方法:
例:
varar= [1,5,23,15,5];
functionf(){
varminarr= [];
this.forEach(function(v,k){
if(v<10){
minarr.push(v);
}
})
returnminarr;
}
Object.getPrototypeOf(ar).min10=f;
console.log(ar.min10());//[1, 5, 5]
// 其他数组对象也具有相应的方法
vara= [1,2,34,7];
console.log(a.min10()); //[1, 2, 7]
这种技术被称为猴子补丁,并且会破坏封装。尽管一些流行的框架(如 Prototype.js)在使用该技术,但仍然没有足够好的理由使用附加的非标准方法来混入内置原型。
3.7 原型对象的问题及使用建议
性能问题:
在原型链上查找属性时是比较消耗资源的,对性能有副作用,这在性能要求苛刻的情况下很重要。
另外,试图访问不存在的属性时会遍历整个原型链。
//声明构造函数Man
function Man(name){
this.name=name;
this.p=function(){
console.log(this.name+'跑');
}
}
var m=newMan('张三');
console.log(m.hasOwnProperty('name')); // true
console.log(m.hasOwnProperty('age')); //false
hasOwnProperty是 JavaScript 中唯一处理属性并且不会遍历原型链的方法。
注意:检查属性是否undefined还不够。该属性可能存在,但其值恰好设置为undefined。
//声明构造函数Man
function Man(name){
this.name=name;
this.n=undefined;
this.p=function(){
console.log(this.name+'跑');
}
}
var m=newMan('张三');
if(m.n==undefined){
console.log('没有n属性')
}
console.log(m.hasOwnProperty('name')); // true
console.log(m.hasOwnProperty('name')); // true
console.log(m.hasOwnProperty('n')); //true
第4章继承
学习目标
· 理解什么是继承
· 原型继承
4.1 什么是继承
· 现实生活中的继承
· 程序中的继承
所谓的继承,其实就是在子类(子对象)能够使用父类(父对象)中的属性及方法;
赋予后辈调用祖辈资源的权限,就是继承;
4.2 原型链继承
//声明构造函数Run
functionRun(){
this.p=function(){
console.log(this.name+'跑');
}
}
//声明构造函数Man
functionMan(name){
this.name=name;
}
//设置构造函数Man的原型为Run,实现继承
Man.prototype=newRun();
varm=newMan('张三');
m.p();
但是,并不建议使用原型链继承,而且JS 中不止有原型链继承,还有其他的继承方式;