JavaScript 中的类和继续

原文宣布在我的博客

我们都晓得 JavaScript 是一门基于原型的言语。当我们挪用一个对象自身没有的属性时,JavaScript 就会从对象的原型对象上去找该属性,假如原型上也没有该属性,那就去找原型的原型,一向找原型链的末尾也就是 Object.prototype 的原型 null。这类属性查找的体式格局我们称之为原型链。

类的完成

由于 JavaScript 自身是没有的类的感念的。所以我们假如要完成一个类,平常是经由过程组织函数来模仿类的完成:

function Person(name,age){  //完成一个类
    this.name = name;
    this.age = age;
}
var you = new Person('you',23); //经由过程 new 来新建实例

起首新建一个 Person 的组织函数,为了和平常的函数区分,我们会运用 CamelCase 体式格局来定名组织函数。
然后经由过程 new 操作符来建立实例,new 操作符实在干了这么几件事:

  1. 建立一个继续自 Person.prototype 的新对象

  2. 组织函数 Person 实行时,响应的参数传入,同时上下文被指定为这个新建的对象。

  3. 假如组织函数返回了一个对象,那末这个对象会庖代 new 的效果。假如组织函数返回的不是对象,则会疏忽这个返回值。

返回值不是对象
function Person(name){
    this.name = name;
    return 'person'
}
var you = new Person('you');
//  you 的值: Person {name: "you"}
返回值是对象
function Person(name){
    this.name = name;
    return [1,2,3]
}
var you = new Person('you');
//  you的值: [1,2,3]

假如类的实例须要同享类的要领,那末就须要给组织函数的 prototype 属性增加要领了。由于 new 操作符建立的对象都继续自组织函数的 prototype 属性。他们能够同享定义在类 prototype 上的要领和属性。

function Person(name,age){  
    this.name = name;
    this.age = age;
}
Person.prototype = {
    sayName: function(){
        console.log('My name is',this.name);
    }
}
var you = new Person('you',23);
var me = new Person('me',23);
you.sayName()   // My name is you.
me.sayName()    // My name is me.

继续的完成

JavaScript 中经常使用的继续体式格局是组合继续,也就是经由过程组织函数和原型链继续同时来模仿继续的完成。

//Person 组织函数如上
function Student(name,age,clas){
    Person.call(this,name,age)
    this.clas = clas;
}
Student.prototype = Object.create(Person.prototype);        // Mark 1
Student.constructor = Student;      //假如不指明,则 Student 会找不到 constructor
Student.prototype.study = function(){
    console.log('I study in class',this.clas)
};
var liming = new Student('liming',23,7);
liming instanceof Person    //true
liming instanceof Student   //true
liming.sayName();       // My name is liming
liming.study();         // I study in class 7

代码中 Mark 1 用到了 Object.create 要领。这个是 ES5 中新增的要领,用来建立一个具有指定原型的对象。假如环境不兼容,能够用下面这个 Polyfill 来完成(仅完成第一个参数)。

if(!Object.create){
    Object.create = function(obj){
        function F(){};
        F.prototype = obj;
        return new F();
    }
}

实在就是把 obj 赋值给暂时函数 F ,然后返回一个 F 的实例。如许经由过程代码 Mark 1 Student 就得到了 Person.prototype 上的一切属性。有人会问了,那末为何不痛快把 Person.prototype 直接赋值给 Student.prototype 呢?

是的,直接赋值是能够到达子类同享父类 prototype 的目标,然则它破坏了原型链。即:子类和父类共用了同一个 prototype,如许当某一个子类修正 prototype 的时刻,实在同时也修正了父类的 prototype,那末就会影响到一切基于这个父类建立的子类,这并非我们想要的效果。看例子:

//Person 同上
//Student 同上
Student.prototype = Person.prototype;
Student.prototype.sayName = function(){
    console.log('My name is',this.name,'my class is',this.clas)
}
var liming = new Student('liming',23,7)
liming.sayName()        //My name is liming,my class is 7;
//另一个子类
function Employee(name,age,salary){
    Person.call(name,age);
    this.salary = salary;
}
Employee.prototype = Person.prototype;
var emp = new Employee('emp',23,10000);
emp.sayName()       //Mark 2

你们猜 Mark 2 会输出什么?

我们希冀的 Mark 2 应该会输出 “My name is emp”. 但实际上报错,为何呢?由于我们改写 Student.prototype 的时刻,也同时修正了 Person.prototype,终究致使 emp 继续的 prototype 是我们所不希冀的,它的 sayName 要领是 My name is',this.name,'my class is',this.clas,如许自然是会报错的。

ES6 的继续

跟着 ECMAScript 6 的宣布,我们有了新的要领来完成继续。也就是经由过程 class 关键字。

类的完成

class Person {
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    sayHello(){
        console.log(`My name is ${this.name},i'm ${this.age} years old`)
    }
}
var you = new Person('you',23);
you.sayHello()      //My name is you,i'm 23 years old.

继续

ES6 内里的继续也很轻易,经由过程 extends 关键字来完成。

class Student extends Person{
    constructor(name,age,cla){
        super(name,age);
        this.class = cla;
    }
    study(){
        console.log(`I'm study in class ${this.class}`)
    }
}
var liming = new Student('liming',23,7)
liming.study()      // I'm study in class 7.

这个继续比拟上面的 ES5 内里完成的继续要轻易了许多,但实在道理是一样的,供应的这些关键字要领只是语法糖罢了,并没有转变 Js 是基于原型这么一个现实。不过 extends 如许完成的继续有一个限定,就是不能定义属性,只能定义要领。要新添属性,照样得经由过程修正 prototype 来到达目标。

Student.prototype.teacher = 'Mr.Li'
var liming = new Student('liming',23,7)
var hanmeimei = new Student('hanmeimei',23,7)
liming.teacher          //Mr.Li
hanmeimei.teacher       //Mr.Li

静态要领

ES6 还供应了 static 关键字,来完成静态要领。静态要领能够继续,但只能由类自身挪用,不能被实例挪用。

class Person{
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    static say(){
        console.log('Static')
    }
}
class Student extends Person{}
Person.say()        // Static
Student.say()       // Static
var you = new Person('you',23);
you.say()           // TypeError: liming.say is not a function

能够看到,在实例上挪用的时刻会直接报错。

Super关键字

在子类中能够经由过程 super 来挪用父类,依据挪用位置的差别,行动也差别。在 constructor 中挪用,相称于挪用父类的 constructor 要领,而在一般要领内里挪用则相称与挪用父类自身。

class Person {
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    sayHello(){
        console.log(`My name is ${this.name},i'm ${this.age} years old`)
    }
}
class Student extends Person{
    constructor(name,age,cla){
        super(name,age);        // 必须在子类挪用 this 前实行,挪用了父类的 constructor
        this.class = cla;       
    }
    sayHello(){
        super.sayHello;         // 挪用父类要领
        console.log('Student say')
    }
}
var liming = new Student('liming',23,7);
liming.say()        // My name is liming,i'm 23 years old.\n Student say.

总结

至此,我们能够看到:在 ES6 宣布今后,JavaScript 中完成继续有了一个规范的要领。虽然它们只是语法糖,背地的实质照样经由过程原型链以及组织函数完成的,不过在写法上更容易于我们明白而且也越发清楚。

参考:

    原文作者:moqiao
    原文地址: https://segmentfault.com/a/1190000004680292
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞