封装
继承
多肽
原型链是面向对象的基础,是很是重要的部分。有如下几种知识:
var obj1 = {name:"江小白"} var obj2 = new Object(name:"江小白")
上面的两种写法,效果是同样的。由于,第一种写法,obj11会指向Object。
var M = function(name){ this.name = name; } var obj3 = new M("asd asd)
var p = {name:'lipiao'} var obj3 = Object.create(p); // 此方法建立的对象是原型链对象
第三种方法,这种方式里,obj3是实例,p是obj3的原型(name是p原型里的属性),构造函数是Objecet 。
- 构造函数经过 new 生成实例
- 构造函数也是函数,构造函数的prototype指向原型。(全部的函数有prototype属性,但实例没有 prototype属性)
- 原型对象中有 constructor,指向该原型的构造函数
- 实例的__proto__指向原型。也就是说,Foo.__proto__ === Foo.prototype。
声明:全部的引用类型(数组、对象、函数)都有__proto__这个属性。
Foo.__proto__ === Function.prototype的结果为true,说明Foo这个普通的函数,是Function构造函数的一个实例。es6
原型链的基本原理:任何一个实例,经过原型链,找到它上面的原型,该原型对象中的方法和属性,能够被全部的原型实例共享。数组
Object对象是原型链的顶端。
原型能够起到继承的做用。原型里的方法均可以被不一样的实例共享:app
//给Foo的原型添加 say 函数 Foo.prototype.say = function () { console.log(''); }
原型链的关键:在访问一个实例的时候,若是实例自己没找到此方法或属性,就往原型上找。若是仍是找不到,继续往上一级的原型上找。函数
instanceof的做用:用于判断实例属于哪一个构造函数。
instanceof的原理:判断实例对象的__proto__属性,和构造函数的prototype属性,是否为同一个引用(是否指向同一个地址)。this
注意1:虽说,实例是由构造函数 new 出来的,可是实例的__proto__属性引用的是构造函数的prototype。也就是说,实例的__proto__属性与构造函数自己无关。 注意2:在原型链上,原型的上面可能还会有原型,以此类推往上走,继续找__proto__属性。这条链上若是能找到, instanceof 的返回结果也是 true。
好比说:es5
foo instance of Foo的结果为true,由于foo.__proto__ === M.prototype为true。
foo instance of Objecet的结果也为true,为Foo.prototype.__proto__ === Object.prototype为true。
但咱们不能轻易的说:foo 必定是 由Object建立的实例`。这句话是错误的。咱们来看下一个问题就明白了。spa
问题:已知A继承了B,B继承了C。怎么判断 a 是由A直接生成的实例,仍是B直接生成的实例呢?仍是C直接生成的实例呢?
分析:这就要用到原型的constructor属性了。prototype
foo.__proto__.constructor === M的结果为true,可是 foo.__proto__.constructor === Object的结果为false。
因此,用 consturctor判断就比用 instanceof判断,更为严谨。
当new一个对象是发生了什么
function Animal(){ this.name="xiaoqi";//经过this,代表这是一个构造函数 }
class Animal{ constructor(){//能够在构造函数里写属性 this.name = name; } }
类的实例化很简单,直接 new 出来便可。code
new Animal();
继承的本质是原型链,
继承是面向对象语言的基础概念,通常面向对象语言支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。ECMAScript中函数没有签名,所以没法实现接口继承。ECMAScript只支持实现继承,而其实现继承主要是靠原型链来实现。
原理:让一个引用类型继承另外一个引用类型的方法和属性
具体实现以下:对象
// 父类 function Parent(){ this.name = "xiaoqi"; this.children = ["zhuzhu","chouzhu"] } Parent.prototype.getChildren = function(){ console.log(this.childen); } // 子类 function Child(){ } Child.prototype = new Parent() var child1 = new Child(); child1.children.push("hanhanzhu") console.log(child1.getChildren())// Array ["zhuzhu", "chouzhu", "hanhanzhu"] var child2 = new Child(); console.log(child2.getChildren())// Array ["hanhan", "chouzhu"]
优势
缺点
原理:使用apply()和call()方法以新对象为上下文执行构造函数,子类构造函数内部调用超类构造函数
具体实现以下:
//盗用构造函数 // 父类 function Parent(name){ this.name = name; this.colors = ["red","yellow"]; this.getName = function(){ return this.name; } } // 子类 function Child(name){ Parent.call(this,name); } var child1 = new Child("xiaoqi"); child1.colors.push("xiaopiao"); console.log(child.colors);//["red","yellow","xiaopiao"] var child2 = new Child("xiaopiao"); child2.colors.push("xiaoqi") console.log(child2.colors)//["red","yellow","xiaoqi"]
优势
建立子类实例时,能够向父类传递参数
能够实现多继承(call多个父类对象)
缺点
原理:经过借用构造函数实现对实例属性的继承。这样,既可以保证可以经过原型定义的方法实现函数复用,又可以保证每一个实例有本身的属性
具体实现以下:
// 组合继承(原型链+借用构造函数的组合继承) function Parent(name,age){ this.name= name; this.age = age; this.colors = ['red','green'] console.log("parent") } Parent.prototype.getColors = function(){ console.log(this.colors); } // 子类 function Child(name,age,grade){ Parent.call(this,name,age)//建立子类实列会折行一次 this.grade = grade; } Child.prototype = new Parent();//指定子类原型会执行一次 Child.prototype.constructor = Child;//校订构造函数 Child.prototype.getName = function(){ console.log(this.name) } var c = new Child("xiaoqi",88,99); console.log(c.getName()); // 输出:“Parent”,"Parent","xiaoqi"
优势
缺点
原理:借助原型能够基于已有的对象建立新对象,同时还不比所以建立自定义类型
具体实现以下:
function object(o){ function F(){}; F.prototype = o; return new F(); }
在object()函数内部,先建立了一个临时性的构造函数,而后将传入的对象做为这个构造函数的原型,最后返回这个临时类型的一个新实例。本质上object()就是完成了一次浅复制操做
var person ={ name:"xiaoqi", friends:["piaopiao","xiaopiao"] } var p1= object(person); p1.name="xiaopiao" p1.friends.push("heihei") var p2=object(person); p2.name = "xiaoqi" p2.friends.push("haha") console.log(p1.name) console.log(person.friends) //["piaopiao","xiaoxiao","heihei","haha"]
ECMAScript5经过新增Object.create()方法规范化了原型式继承,这个方法接收两个参数:一个用做新对象原型的对象和为新对象定义属性的对象
var person ={ name:"xiaoqi", friends:["piaopiao","xiaopiao"] } var p1= Object.create(person); p1.name="xiaopiao" p1.friends.push("heihei") var p2=Object.create(person); p2.name = "xiaoqi" p2.friends.push("haha") console.log(p1.name) console.log(person.friends) //["piaopiao","xiaoxiao","heihei","haha"]
寄生式继承是与原型式继承紧密相关的一种思路,即建立一个仅用于封装继承函数过程的函数,该函数在内部以某种方式来加强对象,最后返回对象
具体实现以下:
function object(obj) { function F(){}; F.prototype = obj; return new F(); } function createAnother(original) { var clone = object(original); // 建立新对象 clone.sayHi = function(){ console.log('hello, world'); // 加强对象,添加属性或法,这里致使方法难以复用问题 } return clone; // 返回新对象 } var person = { name: 'alice', friends: ['Sherly', 'Taissy', 'Vant'] } var p1 = createAnother(person); p1.sayHi(); > "hello, world"
合继承是 JavaScript最经常使用的继承模式,其最大的问题是无论在什么状况下都会调用两次超类构造函数:一次是在建立子类原型时,一次是在子类型构造函数内部。子类型最终会包含超类的所有实例属性。
所谓寄生组合式继承,即经过构造函数来继承属性,经过原型链继承方法,背后的基本思路是:没必要为了指定子类的原型而调用超类的构造函数,咱们所须要的无非就是超类原型的一个副本而已
具体实现以下:
function Parent(name,age){ this.name = name; this.age = age; console.log('parent') } Parent.prototype.getName = function(){ return this.name; } function Child(name,age,grade){ Parent.call(this,name,age); this.grade = grade; } // 寄生组合的方式 // 复制父类的原型对象 function create(original){ function F(){}; F.prototype = original; return new F(); } //建立父类的原型副本,改变子类的原型,同时纠正构造函数 function inherit(subClass,superClass){ var parent = create(superClass.prototype); parent.constructor = subClass; subClass.prototype = parent; } inherit(Child,Parent); var child = new Child("xiaoqi",99,99) // ‘parent’
寄生组合继承的高效率在于它只调用了一次超类构造函数,同时还可以保持原型链不变,可以正常使用 instanceof 和 isPrototypeOf() 寄生组合继承被广泛认为是引用类型最理想的继承方式
寄生组合式继承可以很完美地实现继承,但也不是没有缺点。inherit() 方法中复制了父类的原型,赋给子类,假如子类原型上有自定的方法,也会被覆盖,所以能够经过Object.defineProperty的方式,将子类原型上定义的属性或方法添加到复制的原型对象上,如此,既能够保留子类的原型对象的完整性,又可以复制父类原型
function Parent(name, age){ this.name = name; this.age = age; } Parent.prototype.getName = function(){ console.log(this.name) } function Child(name, age, grade){ Parent.call(this, name, age); this.grade = grade; } function inherit(child, parent){ let obj = parent.prototype; obj.constructor = child; for(let key in child.prototype){ Object.defineProperty(obj, key, { value: child.prototype[key] }) } child.prototype = obj; } inherit(Child,Parent); var child = new Child("xiaoqi",99,99) // ‘parent’
S6中引入了class关键字,class能够经过extends关键字实现继承,还能够经过static关键字定义类的静态方法,这比 ES5 的经过修改原型链实现继承,要清晰和方便不少。
ES5 的继承,实质是先创造子类的实例对象this,而后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制彻底不一样,实质是先将父类实例对象的属性和方法,加到this上面(因此必须先调用super方法),而后再用子类的构造函数修改this。
class Person { //调用类的构造方法 constructor(name, age) { this.name = name this.age = age } //定义通常的方法 showName() { console.log("调用父类的方法") console.log(this.name, this.age); } } let p1 = new Person('kobe', 39) console.log(p1) //定义一个子类 class Student extends Person { constructor(name, age, salary) { super(name, age)//经过super调用父类的构造方法 this.salary = salary } showName() {//在子类自身定义方法 console.log("调用子类的方法") console.log(this.name, this.age, this.salary); } } let s1 = new Student('wade', 38, 1000000000) console.log(s1) s1.showName()
继承方式 | 优势 | 缺陷 |
---|---|---|
原型链继承 | 可以实现函数复用 | 1.引用类型的属性被全部实例共享;2.建立子类时不能向超类传参 |
借用构造函数 | 1. 避免了引用类型的属性被全部实例共享; 2. 能够在子类中向超类传参 | 方法都在构造函数中定义了,每次建立实例都会建立一遍方法,没法实现函数复用 |
组合继承 | 融合了原型链继承和构造函数的优势,是Javascript中最经常使用的继承模式 | 建立子类会调用两次超类的构造函数 |
原型继承 | 在没有必要兴师动众地建立构造函数,而只是想让一个对象与另外一个对象保持相似的状况下,原型式继承彻底能够胜任 | 引用类型的属性会被全部实例共享 |
寄生式继承 | 能够加强对象 | 使用寄生式继承来为对象添加函数,会因为不能作到函数复用形成效率下降,这一点与构造函数模式相似;同时存在引用类型的属性被全部实例共享的缺陷 |
寄生组合继承 | 使用寄生式继承来为对象添加函数,会因为不能作到函数复用形成效率下降,这一点与构造函数模式相似;同时存在引用类型的属性被全部实例共享的缺陷 |