首先要明确的是,JavaScript 是面向对象的语言,但与 Java 、 C# 等语言有别,没有类的概念,而是基于原型链 (即使是ES6的"class"也是基于原型链的一种语法糖)。
理解原型对象
只要创建一个新函数,系统默认为其创建一个 prototype 属性,指向函数的原型对象。默认情况下,所有原型对象都自动获得一个 constructor 属性,指向prototype 属性所在函数得指针。举个简单例子:
function Person(){
this.prop = 'myProp'
}
Person.prototype.name = 'zhangsan';
Person.prototype.sayName = function(){
return this.name
}
var person1 = new Person(); // {prop:myProp}
var person2 = new Person(); // {prop:myProp}
console.log(person1.sayName); // 'zhangsan'
person1.sayName = 'lisi'; // 修改原型对象上属性
console.log(person1.sayName); // 'lisi' 证明变量是共享的
console.log(Person.prototype.constructor === Person); // true
console.log(person1.__proto__ === Person.prototype); // true
上述例子简单证明了原型链内的一些规则:
更简单的原型语法
上述例子中,如果要对"Person.prototype"添加多个属性方法,大可不必一个个列出,因为"prototype"属性指向的就是一个对象,可用如下语法写:
function Person(){}
Person.prototype = {
name: 'zhangsan',
age: 24,
job: 'dazha',
// ...
}
// 直接赋值方式会导致 prototype 中默认提供的 constructor 属性消失,可显示指定
Person.prototype.constructor = Person
但这种方式设定的"constructor"值的[[Enumrable]]为true,建议使用Object.defineProperty()。
判断/获取对象的原型对象
继续以上面例子,有两种方法可以判断对象的原型:
var Person_prototype = Object.getPrototypeOf(person1);
console.log(Person.prototype === Person_prototype); // true
console.log(person1 instanceof Person); // true
原型链
关于原型链的定义不多说,大概就是子对象能从父对象读取到属性和方法。先看一个简单例子:
function Parent(){
this.age = 50;
}
Parent.prototype.name = 'Parent';
Parent.prototype.getAge = function(){
return this.age
}
function Son(){
this.age = 24;
}
// 创建匿名对象用于继承
Son.prototype = new Parent()
let son = new Son();
console.log(parent.getAge()); // 24
上述例子中,"son"从"parent"获取"getAge"方法,用于返回自身"age"属性的值,期间大概经历了如下几步:
上述例子的关系图如下:
如果是"更长"的原型链,是同样的原理,直到最后指向"null"后停止。
原型链的简单创造
1. 使用 "Object.create(
let parent = {
age: 50,
getAge() {
return this.age
}
}
let son = Object.create(parent, {
// 属性构造
age: {
value: 24
}
});
console.log(son.age); // 24
2. 使用 "Object.setPrototypeOf(obj, prototype)" 方法:
let obj1 = { a: 1 }
let obj2 = { b: 2 }
Object.setPrototypeOf(obj1, obj2); // {a:1}
console.log(obj1.b); // 2
ES6 的 "class" 关键字
ES6的"class"关键字本质是ES5原型链的语法糖,更详细介绍请看 阮一峰的ES6 。
class Parent {
// 父类构造函数
constructor(firstname, lastname) {
this.firstname = firstname;
this.lastname = lastname
}
say() {
return 'my name is '+this.lastname + this.firstname
}
// 静态方法
static toString() {
console.log('Parent static method')
}
}
// 子类,使用"extents"关键字继承
class Son extends Parent{
constructor(firstname, lastname, age){
// 调用"super"方法前无法使用"this"
super(firstname, lastname)
this._age = age;
}
say(){
// "super"可获取父类的方法
let result = super.say()
return result + '. my age is ' + this._age
}
// getter方法,setter同理
get age(){
return this._age
}
// 同名静态方法,当此方法未定义时,会调用父类的静态方法
static toString() {
console.log('Son static method')
}
}
let parent = new Parent('san', 'zhang'); // "new"创建实例对象
console.log(parent.say()); // 'my name is zhangsan'
Parent.toString(); // 'Parent static method'
let son = new Son('ergou','zhang',16); // 子类的实例
console.log(son.say()); // 'my name is zhangergou. my age is 16'
Son.toString(); // 'Son static method'
console.log(son.age); // 16
"class" 有如下特征:
原型链存在的问题
上例子可以看出,实例对象"person1" 和 "person2" 均能访问原型对象上的属性方法,但是实例对象却能对其修改,导致所有实例对象均产生影响。解决办法是,在实例对象上定义同名属性,则会优先调用实例对象上的属性。