前导知识

进一步了解之前先要分清楚构造函数私有属性和方法、静态属性和方法、实例属性和方法、原型属性和方法。

// 构造函数
function Person(name) {
    // 私有属性
    let age = 25
    // 私有方法          
    function sex() {
        console.log('male')
    }
    //闭包
    this.getInner = function () {
        console.log(age);
    }
    // 实例属性
    this.name = name || 'someName';
    // 实例方法
    this.sleep = function () {
        console.log(this.name + '在睡觉');
    }
}

// 静态属性
Person.from = 'guangzhou'
// 静态方法    
Person.run = function () {
    console.log("在跑步");
}

//原型属性
Person.prototype.height = 180
// 原型方法
Person.prototype.eat = function (food) {
    console.log(this.name + '在吃' + food);
};

//实例对象
let p1 = new Person("p1");

私有属性/方法

私有属性/方法由于定义在函数内,存在与构造函数的函数作用域,函数外部只能通过闭包的方式访问。

p1.getInner() //25

实例属性/方法

只有通过new运算符新建的对象才能访问,因为实例属性/方法被this赋给了新建的对象。

Person.sleep() //Person.sleep is not a function
p1.sleep() //p1在睡觉

静态属性/方法

即构造函数本身添加的成员,只能被构造函数访问,不能被实例对象访问。

Person.run() //在跑步
p1.run() //p1.run is not a function

原型属性/方法

原型中的属性/方法实例对象和构造函数都可以访问到。

p1.eat("food") //p1在吃food
Person.prototype.eat("food") //undefined在吃food,这里如果不用.prototype就是访问Person的静态方法
Person.eat() //undefined 直接访问函数的静态属性和方法时,如果找不到,不会去函数的原型上查找

总结

  • 函数的私有属性和方法在函数体外部是不能直接通过属性访问符.访问的,如果要访问,可以使用闭包
  • 函数的静态属性和方法可以在函数体外部直接访问
  • 函数的实例属性和方法是绑定到函数返回的实例上的,当然,函数得作为构造函数使用new调用
  • 函数的原型属性和方法是挂在函数的原型对象上的,实例的原型对象会关联到函数的原型对象,当访问实例的属性和方法时,如果实例上找不到,会通过原型委托到函数的原型上。但是,当直接访问函数的静态属性和方法时,如果找不到,不会去函数的原型上查找

类的创建/继承

原型继承

在上个小节的代码块中,已经通过new和构造函数生成实例对象,既创造了一个类,这个类有自己的实例属性和方法,同时通过prototype让每个实例继承原型属性和方法。

特点:基于原型链,既是父类的实例,也是子类的实例

缺点:无法实现多继承

构造继承

使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

//父类构造函数
function Person(name) {
    // 父实例属性
    this.name = name || 'someName';
    // 父实例方法
    this.sleep = function () {
        console.log(this.name + '在睡觉');
    }
}
//父原型属性
Person.prototype.height = 180
//子类构造函数
function PersonChild(name) {
    Person.call(this);
    this.name = name || 'someChildName';
}
let personChild = new PersonChild();
console.log(personChild.name); // someChildName
console.log(personChild.height); // undefined
personChild.sleep(); // someChildName在睡觉
console.log(personChild instanceof Person); // false
console.log(personChild instanceof PersonChild); // true

特点:可以实现多继承

缺点:只能继承父类实例属性和方法,不能继承原型上的属性和方法

组合继承

相当于构造继承和原型链继承的组合体。通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Person(name) {
    // 实例属性
    this.name = name || 'someName';
    // 实例方法
    this.sleep = function () {
        console.log(this.name + '在睡觉');
    }
}
//原型属性
Person.prototype.height = 180

function PersonChild(name) {
    Person.call(this);
    this.name = name || 'someChildName';
}

PersonChild.prototype = new Person();
PersonChild.prototype.constructor = PersonChild;
let personChild = new PersonChild();

console.log(personChild.name); // someChildName
console.log(personChild.height); // 180
personChild.sleep() // someChildName在睡觉
console.log(personChild instanceof Person); // true
console.log(personChild instanceof PersonChild); // true

特点:可以继承实例属性/方法,也可以继承原型属性/方法

缺点:调用了两次父类构造函数,生成了两份实例

寄生组合继承

通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性

function Person(name) {
    // 实例属性
    this.name = name || 'someName';
    // 实例方法
    this.sleep = function () {
        console.log(this.name + '在睡觉');
    }
}
//原型属性
Person.prototype.height = 180

function PersonChild(name) {
    Person.call(this);
    this.name = name || 'someChildName';
}

let Super = function(){};
Super.prototype = Person.prototype;
PersonChild.prototype = new Super();
let personChild = new PersonChild();

console.log(personChild.name); // someChildName
console.log(personChild.height); // 180
personChild.sleep() // someChildName在睡觉
console.log(personChild instanceof Person); // true
console.log(personChild instanceof PersonChild); // true

ES6的方式

创建

class Person {
    // 实例属性和方法
    constructor(name) {
        this.name = name || 'someName';
        this.fn = function (params) {
            console.log(params);
        }
    }
    //原型方法
    sleep() {
        console.log(this.name + '在睡觉');
    }
}

let p1 = new Person('someName');
p1.sleep(); //someName在睡觉

class中的constructor函数负担起了之前的构造函数的功能,类中的实例属性和方法都可以在这里初始化。ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到。

继承

class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

class Person {
    // 实例属性和方法
    constructor(name) {
        this.name = name || 'someName';
        this.fn = function (params) {
            console.log(params);
        }
    }
    //原型方法
    sleep() {
        console.log(this.name + '在睡觉');
    }
}

class PersonChild extends Person {
    constructor(name) {
        super(name);
    }
}
let personChild = new PersonChild('someName');
personChild.sleep(); // someName在睡觉,父类原型方法
personChild.fn("父类实例方法") // 父类实例方法
console.log(personChild instanceof Person); // true
console.log(personChild instanceof PersonChild); // true

super关键字,表示父类的构造函数,用来新建父类的this对象。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

注意:

  • 父类的静态方法,也会被子类继承。
  • 如果要在子类的方法中调用父类方法,需要用super.fn()调用。
  • ES6的继承与ES5实现的类继承,还有一点不同。ES5是先创建子类的实例,然后在子类实例的基础上创建父类的属性。而ES6是相反的,先创建父类的实例,然后在父类实例的基础上扩展子类属性。利用这个属性可以做到一些ES5无法实现的功能:继承原生对象