面向对象

1. 面向对象的三大个性

封装
继承
多肽

1.1 原型链的常识

原型链是面向对象的根底,是十分重要的局部。有以下几种常识:

2. 创立原型的几种办法

2.1 形式一:字面量

var obj1 = {name:"江小白"}var obj2 = new Object(name:"江小白")
下面的两种写法,成果是一样的。因为,第一种写法,obj11会指向Object。
  • 第一种写法是:字面量的形式。
  • 第二种写法是:内置的构造函数

2.2 形式二:通过构造函数

var M = function(name){    this.name = name;}var obj3 = new M("asd asd)

2.3 办法三:Object.create

var p = {name:'lipiao'}var obj3 = Object.create(p);// 此办法创立的对象是原型链对象
第三种办法,这种形式里,obj3是实例,p是obj3的原型(name是p原型里的属性),构造函数是Objecet 。

3. 原型链

  1. 构造函数通过 new 生成实例
  2. 构造函数也是函数,构造函数的prototype指向原型。(所有的函数有prototype属性,但实例没有 prototype属性)
  3. 原型对象中有 constructor,指向该原型的构造函数

  1. 实例的__proto__指向原型。也就是说,Foo.__proto__ === Foo.prototype。

申明:所有的援用类型(数组、对象、函数)都有__proto__这个属性。
Foo.__proto__ === Function.prototype的后果为true,阐明Foo这个一般的函数,是Function构造函数的一个实例。

3.2 原型链

原型链的基本原理:任何一个实例,通过原型链,找到它下面的原型,该原型对象中的办法和属性,能够被所有的原型实例共享。

Object对象是原型链的顶端。

原型能够起到继承的作用。原型里的办法都能够被不同的实例共享:

//给Foo的原型增加 say 函数  Foo.prototype.say = function () {      console.log('');  }

原型链的要害:在拜访一个实例的时候,如果实例自身没找到此办法或属性,就往原型上找。如果还是找不到,持续往上一级的原型上找。

3.3 instanceof的原理


instanceof的作用:用于判断实例属于哪个构造函数。
instanceof的原理:判断实例对象的__proto__属性,和构造函数的prototype属性,是否为同一个援用(是否指向同一个地址)。

    留神1:尽管说,实例是由构造函数 new 进去的,然而实例的__proto__属性援用的是构造函数的prototype。也就是说,实例的__proto__属性与构造函数自身无关。    留神2:在原型链上,原型的下面可能还会有原型,以此类推往上走,持续找__proto__属性。这条链上如果能找到, instanceof 的返回后果也是 true。

比如说:

foo instance of Foo的后果为true,因为foo.__proto__ === M.prototype为true。
foo instance of Objecet的后果也为true,为Foo.prototype.__proto__ === Object.prototype为true。
但咱们不能轻易的说:foo 肯定是 由Object创立的实例`。这句话是谬误的。咱们来看下一个问题就明确了。

3.4 剖析一个问题

问题:已知A继承了B,B继承了C。怎么判断 a 是由A间接生成的实例,还是B间接生成的实例呢?还是C间接生成的实例呢?

剖析:这就要用到原型的constructor属性了。

foo.__proto__.constructor === M的后果为true,然而 foo.__proto__.constructor === Object的后果为false。
所以,用 consturctor判断就比用 instanceof判断,更为谨严。

4. new运算符

当new一个对象是产生了什么
  • 创立一个新的空对象实例。
  • 将此空对象的隐式原型指向其构造函数的显示原型。
  • 执行构造函数(传入相应的参数,如果没有参数就不必传),同时 this 指向这个新实例。
  • 如果返回值是一个新对象,那么间接返回该对象;如果无返回值或者返回一个非对象值,那么就将步骤(1)创立的对象返回。

5 类的定义、类的申明(继承的实质:原型链)

5.1形式一、用构造函数模仿类(es5)

function Animal(){    this.name="xiaoqi";//通过this,表明这是一个构造函数}

5.2形式二、用class申明(es6)的写法

class Animal{    constructor(){//能够在构造函数里写属性        this.name = name;    }}

5.3 类的实列化

类的实例化很简略,间接 new 进去即可。

new Animal();

5.4继承的几种形式

继承的实质是原型链,
继承是面向对象语言的根底概念,个别面向对象语言反对两种继承形式:接口继承和实现继承。接口继承只继承办法签名,而实现继承则继承理论的办法。ECMAScript中函数没有签名,因而无奈实现接口继承。ECMAScript只反对实现继承,而其实现继承次要是靠原型链来实现。

5.4.1 原型链

原理:让一个援用类型继承另一个援用类型的办法和属性

具体实现如下:

// 父类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"]
长处
  • 父类新增原型办法/原型属性,子类都能拜访到
  • 简略,易于实现
毛病
  • 无奈实现多继承
  • 援用类型的属性被所有实例共享
  • 在创立Child的实例的时候,不能向Parent传参

5.4.2 盗用构造函数

原理:应用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多个父类对象)

毛病
  • 只能继承父类的实例属性和办法,不能继承原型属性和办法
  • 每次实列都会创立一遍办法,函数复用是一个问题

5.4.3 组合继承(原型链+借用构造函数的组合继承)

原理:通过借用构造函数实现对实例属性的继承。这样,既可能保障可能通过原型定义的办法实现函数复用,又可能保障每个实例有本人的属性

具体实现如下:

//  组合继承(原型链+借用构造函数的组合继承)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"
长处
  • 能够继承实例属性/办法,也能够继承原型属性/办法
  • 不存在援用属性共享问题
  • 可传参
  • 函数可复用
毛病
  • 创立子类时会调用两次超类的构造函数

5.4.4 原型式继承

原理:借助原型能够基于已有的对象创立新对象,同时还不比因而创立自定义类型

具体实现如下:

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()办法规范化了原型式继承,这个办法接管两个参数:一个用作新对象原型的对象和为新对象定义属性的对象
  • 留神Object.create()有两个参数,第二个与Object.defineProperties()的第二个参数一样
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"]

5.4.5 寄生式继承

寄生式继承是与原型式继承严密相干的一种思路,即创立一个仅用于封装继承函数过程的函数,该函数在外部以某种形式来加强对象,最初返回对象

具体实现如下:

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"

5.4.6 寄生组合式继承

合继承是 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() 寄生组合继承被普遍认为是援用类型最现实的继承形式

5.4.7 增强型寄生组合继承

寄生组合式继承可能很完满地实现继承,但也不是没有毛病。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’

5.4.8 ES6中class 的继承

S6中引入了class关键字,class能够通过extends关键字实现继承,还能够通过static关键字定义类的静态方法,这比 ES5 的通过批改原型链实现继承,要清晰和不便很多。

ES5 的继承,本质是先发明子类的实例对象this,而后再将父类的办法增加到this下面(Parent.apply(this))。ES6 的继承机制齐全不同,本质是先将父类实例对象的属性和办法,加到this下面(所以必须先调用super办法),而后再用子类的构造函数批改this。

  • class关键字只是原型的语法糖,JavaScript继承依然是基于原型实现的
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中最罕用的继承模式创立子类会调用两次超类的构造函数
原型继承在没有必要调兵遣将地创立构造函数,而只是想让一个对象与另一个对象放弃相似的状况下,原型式继承齐全能够胜任援用类型的属性会被所有实例共享
寄生式继承能够加强对象应用寄生式继承来为对象增加函数,会因为不能做到函数复用造成效率升高,这一点与构造函数模式相似;同时存在援用类型的属性被所有实例共享的缺点
寄生组合继承应用寄生式继承来为对象增加函数,会因为不能做到函数复用造成效率升高,这一点与构造函数模式相似;同时存在援用类型的属性被所有实例共享的缺点