functionPerson(name,age){ //实现一个类 this.name = name; this.age = age; } var you = new Person('you',23); //通过 new 来新建实例
首先新建一个 Person 的构造函数,为了和一般的函数区别,我们会使用 CamelCase 方式来命名构造函数。 然后通过 new 操作符来创建实例,new 操作符其实干了这么几件事:
创建一个继承自 Person.prototype 的新对象
构造函数 Person 执行时,相应的参数传入,同时上下文被指定为这个新建的对象。
如果构造函数返回了一个对象,那么这个对象会取代 new 的结果。如果构造函数返回的不是对象,则会忽略这个返回值。
返回值不是对象 functionPerson(name){ this.name = name; return'person' } var you = new Person('you'); // you 的值: Person {name: "you"} 返回值是对象 functionPerson(name){ this.name = name; return [1,2,3] } var you = new Person('you'); // you的值: [1,2,3]
如果类的实例需要共享类的方法,那么就需要给构造函数的 prototype 属性添加方法了。因为 new 操作符创建的对象都继承自构造函数的 prototype 属性。他们可以共享定义在类 prototype 上的方法和属性。
functionPerson(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.
//Person 构造函数如上 functionStudent(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 来实现(仅实现第一个参数)。
//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; //另一个子类 functionEmployee(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 关键字。
类的实现
classPerson{ 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 关键字来实现。
classStudentextendsPerson{ 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.
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
classPerson{ constructor(name,age){ this.name = name; this.age = age; } staticsay(){ console.log('Static') } } classStudentextendsPerson{} 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 方法,而在普通方法里面调用则相当与调用父类本身。
classPerson{ constructor(name,age){ this.name = name; this.age = age; } sayHello(){ console.log(`My name is ${this.name},i'm ${this.age} years old`) } } classStudentextendsPerson{ 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.