原型、继承
原型prototype
原型链
实现对象和原型对象的链接
function Person(){
// this.number = 10;
}
Person.prototype.number = 20;
var p = new Person();
alert(p.number);
p ==> 构造函数 ==> 原型对象 ==> Object.prototype
在执行“alert(p.number)”时,首先去p的构造函数Person中找是否有number这个属性
如果找不到就去原型对象Perosn.prototype中找,
还是找不到就去Object的原型对象找
原型的默认方法和属性
hasOwnProperty
判断某个属性是不是对象自己下面的属性,返回boolean类型
原型下的属性不是
例如
function Person(){
this.number = 10;
}
Person.prototype.number2 = 20;
var p = new Person();
p.num = 5;
alert( p.hasOwnProperty('number') ); // true
alert( p.hasOwnProperty('num') ); // true
alert( p.hasOwnProperty('number2') ); // false
constructor
属性,值就是构造函数,是原型下的构造器
当new一个对象的时候,原型里面默认带有很多属性和函数,但是不能 for...in 遍历
只能遍历出我们自己给它的原型绑定的一些属性和方法
alert( p.constructor ); // function Person(){this.number = 10;}
// constructor的应用:判断数据类型
var arr = [1, 2, 3];
// alert( arr.constructor.name ); // Array
// alert( arr.constructor == Array ); // true
for(var key in Person.prototype){
alert(key);
} // number2
instanceof
判断前面的对象是否是后面类的实例
alert( p instanceof Person ); // true
var o = new Object();
alert( o instanceof Person ); // false
toString
把一个对象转变成字符串
alert( typeof arr.toString() + '=====' + arr.toString() ); // string=====1,2,3
// 可以传一个参数,进行进制转换
var num = 3;
alert(num.toString(2)); // 3转为二进制 ==> 11
num = 255;
alert(num.toString(16)); // ff
继承
继承:
儿子继承爸爸
儿子可以用爸爸的方法和属性
儿子的改变不能影响爸爸
拷贝继承
function Person(name){
this.name = name;
}
Person.prototype.showName = function(){
alert(this.name);
}
var p1 = new Person('aa');
// p1.showName(); // aa
function PersonAge(name, age){
Person.call(this, name);
this.age = age;
}
// 拷贝继承 ==> 拷贝父类原型对象的方法
PersonAge.prototype.showName = function(){
alert(this.name);
}
// 缺点很明显,当父类下的原型方法很多的时候,就得一个个拷贝
原型继承
// 解决上面问题的方法:将子类原型 指向 父类原型,这样子类就继承了父类原型对象下的所有方法和属性
PersonAge.prototype = Person.prototype;
var p2 = new PersonAge('bb', 18);
// p2.showName(); // bb
PersonAge.prototype.showAge = function(){
alert(this.age);
}
p2.showAge(); // 18
p1.showAge(); // undefined 说明父类也具有了该方法,这样就有问题啦,影响到父类了,
// 原因是PersonAge.prototype = Person.prototype;
怎么解决呢?可以将父类原型对象通过一个中间对象复制一份,然后让子类原型指向该副本
function extend(obj){
var newObj = {};
for(var key in obj){
newObj[key] = obj[key];
}
return newObj;
}
// 原型继承
PersonAge.prototype = extend(Person.prototype);
var p2 = new PersonAge('bb', 18);
PersonAge.prototype.showAge = function(){
alert(this.age);
}
// p2.showAge(); // 18
// p1.showAge(); // Uncaught TypeError: p1.showAge is not a function ^_^
但是,新的问题又出现了,那就是子类实例的构造器就变为了Object,这是由于我们是“通过一个中间对象(Object)复制一份”父类原型给子类的,所以子类原型实际指向的是该Object
alert(p2.constructor); // function Object() { [native code] } 不对啦,原有的构造器的方法没有了
怎么解决?可以将子类原型对象作为这个“中间对象”
function extend(obj, newObj){
for(var key in obj){
newObj[key] = obj[key];
}
return newObj;
}
PersonAge.prototype = extend(Person.prototype, PersonAge.prototype);
PersonAge.prototype.showAge = function(){
alert(this.age);
}
var p2 = new PersonAge('bb', 18);
alert(p2.constructor);
p2.showAge(); // 18
p1.showAge(); // Uncaught TypeError: p1.showAge is not a function ^_^
类式继承
function Person(name){ // 父类
this.name = name;
}
Person.prototype.showName = function(){ // 父类原型方法
alert(this.name);
}
var p1 = new Person('aa');
// 先让子类继承父类的构造函数中的方法和属性
function PersonAge(name, age){ // 子类
Person.call(this, name);
this.age = age;
}
// 再通过一个中间空函数的原型指向父类的原型,然后将子类原型指向新创建(new)出的“中间空函数”,这样子类就间接继承了父类原型下面的所有方法和属性
var Fn = function(){}
Fn.prototype = Person.prototype;
PersonAge.prototype = new Fn();
var p2 = new PersonAge('bb', 18);
// p2.showName(); // bb
// 给子类原型添加方法
PersonAge.prototype.showAge = function(){
alert(this.age);
}
// alert( p1.showAge ); // undefined,说明子类没有影响到父类
// 但是子类构造器也会间接指向父类的构造器
// alert( p2.constructor ); // function Person(name){this.name=name} 就是和父类的构造
// 怎么解决 ==> 直接将子类构造函数赋给子类的构造器即可
PersonAge.prototype.constructor = PersonAge;
alert( p2.constructor ); // function PersonAge(name, age){...}
instance之安全模式
var Person = function(name, age){
this.name = name;
this.age = age;
}
var p = Person('aa', 18); // 这里其实是方法的调用,默认当然就是返回undefined,传递的参数实际是window下的
console.log(p + ' <==> ' + name + ' <==> ' + age); // undefined <==> aa <==> 18
// 改写:安全模式
var Person = function(name, age){
if(this instanceof Person){
this.name = name;
this.age = age;
}else{
return new Person(name, age);
}
}
var p = Person('aa', 18);
// console.log(p + ' <==> ' + name + ' <==> ' + age); // age is not defined ^_^
console.log(p + ' <==> ' + p.name + ' <==> ' + p.age); // [object Object] <==> aa <==> 18
继承总结
类式继承
类式继承的原理:
新创建的对象复制了父类构造函数内的属性与方法,并且将原型_proto_指向了父类的原型对象,这样就拥有了父类原型对象上的属性与方法
子类的原型同样可以访问父类原型上的属性和方法,以及从父类构造函数中复制的属性和方法
function SuperClass(){
this.books = ['javascript', 'html', 'css'];
}
function SubClass(){
}
SubClass.prototype = new SuperClass();
/*
instanceof 可以用来判断对象的prototype链中,某个对象是否是某个类的实例
*/
var instance = new SubClass();
/*
console.log(instance instanceof SubClass); // true
console.log(instance instanceof SuperClass); // true
console.log(SubClass instanceof SuperClass); // false
console.log(SubClass.prototype instanceof SuperClass); // true
console.log(instance instanceof Object); // true
*/
// 缺陷:会影响到其他实例对象的引用变量
var instance1 = new SubClass();
var instance2 = new SubClass();
console.log(instance2.books); // ["javascript", "html", "css"]
instance1.books.push('设计模式');
console.log(instance2.books); // ["javascript", "html", "css", "设计模式"]
构造函数继承
// 声明父类
function SuperClass(id){
this.books = ['javascript', 'html', 'css'];
this.id = id;
}
// 父类声明原型方法
SuperClass.prototype.showBooks = function(){
console.log(this.books);
}
// 声明子类
function SubClass(id){
// 继承父类
SuperClass.call(this, id);
}
// 创建第一个子类的实例
var instance1 = new SubClass(0);
// 创建第二个子类的实例
var instance2 = new SubClass(1);
instance1.books.push('设计模式');
console.log(instance1.books, instance1.id); // ["javascript", "html", "css", "设计模式"] 0
console.log(instance2.books, instance2.id); // ["javascript", "html", "css"] 1
// 缺陷
// 构造函数没有涉及prototype,故父类的原型方法不会被子类继承
console.log(instance2.showBooks()); // Uncaught TypeError: instance2.showBooks is not a function
组合式继承
组合式继承 = 类式继承 + 构造函数继承
// 声明父类
function SuperClass(name){
this.name = name;
this.books = ['javascript', 'html', 'css'];
}
// 父类声明原型方法
SuperClass.prototype.getName = function(){
console.log(this.name);
}
// 声明子类
function SubClass(name, time){
SuperClass.call(this, name); // 第一次利用SuperClass构造函数
this.time = time;
}
// 类式继承 子类的原型继承父类
SubClass.prototype = new SuperClass(); // 第二次利用SuperClass构造函数
SubClass.prototype.getTime = function(){
console.log(this.time);
}
var instance1 = new SubClass('js', 2014);
instance1.books.push('设计模式');
console.log(instance1.books); // ["javascript", "html", "css", "设计模式"]
instance1.getTime(); // 2014
instance1.getName(); // js
var instance2 = new SubClass('php', 2013);
console.log(instance2.books); // ["javascript", "html", "css"]
instance2.getTime(); // 2013
instance2.getName(); // php
var instance3 = new SuperClass('java');
console.log(instance3.books); // ["javascript", "html", "css"]
instance3.getName(); // java
// instance3.getTime(); // Uncaught TypeError: instance3.getTime is not a function
console.log(instance1.constructor.name); // SuperClass
console.log(instance1.constructor.name); // SuperClass
// 解决办法
SubClass.prototype.constructor = SubClass;
console.log(instance1.constructor.name); // SubClass
console.log(instance1.constructor.name); // SubClass
缺点:构造函数开销大
有没有更好的方法?
==> 寄生组合式继承方式
寄生组合式继承
寄生组合式继承
通过在一个函数内的过渡对象实现继承并且返回新对象的方式称为寄生式继承,此时再结合构造函数式继承,就称为寄生组合式继承
1. 寄生式继承依托于原型继承
2. 构造函数式继承
回顾一下
// 原型继承
// 值类型属性被复制,引用类型的属性被公用
function extend(obj){
// 声明一个过渡函数对象
var F = function(){};
// 过渡对象的原型继承父对象
F.prototype = obj;
// 返回过渡对象的实例,并且该实例的原型继承了父对象
return new F();
}
var book = {
name: 'js', // 值类型
books: ['css', 'java'] // 引用类型
};
var instance1 = extend(book);
console.log(instance1.name); // js
console.log(instance1.books); // ['css', 'java']
console.log(instance1.constructor); // ƒ Object() { [native code] }
instance1.name = 'jsp';
instance1.books.push('ajax');
console.log(instance1.name, instance1.books); // jsp (3) ["css", "java", "ajax"]
console.log(book.name, book.books); // js (3) ["css", "java", "ajax"]
function extend(obj){
// 声明一个过渡函数对象
var F = function(){};
// 过渡对象的原型继承父对象
F.prototype = obj;
// 返回过渡对象的实例,并且该实例的原型继承了父对象
return new F();
}
function extendPrototype(subClass, superClass){
// 复制一份父类的原型副本保存在变量中
var p = extend(superClass.prototype);
// 将构造器重新指向子类
p.constructor = subClass;
subClass.prototype = p;
}
// 定义父类
function SuperClass(name){
this.name = name;
this.colors = ['red', 'blue', 'yellow'];
}
// 定义父类原型方法
SuperClass.prototype.getName = function(){
console.log(this.name);
}
// 定义子类
function SubClass(name, time){
// 构造函数继承
SuperClass.call(this, name);
// 子类新增属性
this.time = time;
}
// 寄生式继承父类原型
extendPrototype(SubClass, SuperClass);
SubClass.prototype.getTime = function(){
console.log(this.time);
}
// 举例
var instance1 = new SuperClass('js');
var instance2 = new SubClass('css', 2016);
instance1.colors.push('pink');
console.log(instance1.colors); // ["red", "blue", "yellow", "pink"]
console.log(instance2.colors); // ["red", "blue", "yellow"]
instance2.getName(); // css
instance2.getTime(); // 2016
instance1.getName(); // js
instance1.getTime(); // Uncaught TypeError: instance1.getTime is not a function
几种继承方式比较
// 先定义一个父类
function Animal(name){
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + ' is sleeping!');
}
}
// 原型方法
Animal.prototype.eat = function(food){
console.log(this.name + ' is eating: ' + food);
}
原型链继承
核心:将父类的实例作为子类的原型
function Cat(){}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name); // cat
console.log(cat.eat('fish')); // cat is eating: fish
console.log(cat.sleep()); // cat is sleeping!
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
特点:
1)非常纯粹的继承关系,实例是子类的实例,也是父类的实例
2)父类新增原型方法/原型属性,子类都能访问到
3)简单,易于实现
缺点:
1)要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
2)无法实现多继承
3)来自原型对象的所有属性被所有实例共享
4)创建子类实例时,无法向父类构造函数传参
推荐指数:★★(3、4两大致命缺陷)
构造继承
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name); // Tom
console.log(cat.sleep()); // Tom is sleeping!
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
特点:
1)解决了1中,子类实例共享父类引用属性的问题
2)创建子类实例时,可以向父类传递参数
3)可以实现多继承(call多个父类对象)
缺点:
1)实例并不是父类的实例,只是子类的实例
2)只能继承父类的实例属性和方法,不能继承原型属性/方法
3)无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
推荐指数:★★(缺点3)
实例继承
核心:为父类实例添加新特性,作为子类实例返回
function Cat(name){
var instance = new Animal();
instance.name = name || 'Tom';
return instance;
}
// Test Code
var cat = new Cat();
console.log(cat.name); // Tom
console.log(cat.sleep()); // Tom is sleeping!
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // false
特点:
不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果
缺点:
1)实例是父类的实例,不是子类的实例
2)不支持多继承
推荐指数:★★
拷贝继承
function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
Cat.prototype.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name); // Tom
console.log(cat.sleep()); // Tom is sleeping!
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
特点:
支持多继承
缺点:
效率较低,内存占用高(因为要拷贝父类的属性)
无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
推荐指数:★(缺点1)
组合继承
核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复制
function Cat(name){
Animal.call(this); // 第一次实例化Animal
this.name = name || 'Tom';
}
Cat.prototype = new Animal(); // 第二次实例化Animal
// 修复构造器指向
Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name); // Tom
console.log(cat.sleep()); // Tom is sleeping!
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
特点:
1)弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
2)既是子类的实例,也是父类的实例
3)不存在引用属性共享问题
4)可传参
5)函数可复用
缺点:
调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)
推荐指数:★★★★(仅仅多消耗了一点内存)
寄生组合继承
核心:通过寄生方式,砍掉父类的实例属性,这样在调用两次父类的构造的时候,就不会初始化两次实例方法和属性,避免了组合继承的缺点
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
(function(){
// 创建一个没有实例方法的类
var Super = function(){}
Super.prototype = Animal.prototype;
// 将实例作为子类的原型
Cat.prototype = new Super();
})();
// 修复构造器指向
Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name); // Tom
console.log(cat.sleep()); // Tom is sleeping!
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
特点:
堪称完美
缺点:
实现较为复杂
推荐指数:★★★★(实现复杂,扣掉一颗星)