基本都是廖学峰老师JavaScript教程的内容。先前看也似是懂了,自己稍微理下作复习。
1、原型链
JavaScript 的原型链和 Java 的 Class 区别就在, 它没有“Class”的概念, 所有对象都是实例,所谓继承关系不过是把一个对象的原型指向另一个对象而已。
Student
是一个现有的对象,不同于C++,Python中类对象的概念,Student
更符合实例对象。Student
有name
属性和run()
方法。xm
只有name
属性,Student
和xm
是同等地位的实例对象。xm.__proto__ = Student;
把xm
的原型指向了对象Student
,看上去 xm
仿佛是从Student
继承下来的。xm
可以调用Student
的run()
方法。
var Student = {
name: 'Robert',
run: function() {
console.log(this.name + ' is running...');
}
};
var xm = {
name: 'xiaoming'
}
xm.__proto__ = Student;
console.log(xm.name);
xm.run();
console.log(Student.name);
Student.run();
// run output:
// xiaoming
// xiaoming is running...
// Robert
// Robert is running...
上述代码仅用于演示目的。在编写 JavaScript 代码时,不要直接用 obj.__proto__
去改变一个对象的原型,并且,低版本的IE也无法使用__proto__
。
Object.create()
方法可以传入一个原型对象,并创建一个基于该原型的新对象, 但是新对象什么属性都没有。可以编写一个函数来创建新对象。
xm
对象的__proto__
属性是{ name: 'Robert', run: [Function: run] }
,而没有prototype
属性。
var Student = {
name: 'Robert',
run: function() {
console.log(this.name + ' is running...');
}
};
function createStudent(name) {
// 传入一个原型对象,创建一个新的原型对象
var s = Object.create(Student);
s.name = name;
return s;
}
var xm = createStudent('xiaoming');
xm.run() // xiaoming is running...
// xm 对象的__proto__ 和 prototype
console.log(xm.__proto__) //{ name: 'Robert', run: [Function: run] }
console.log(xm.prototype) // undefined
// 原型链
console.log(xm.__proto__ === Student);
console.log(Student.__proto__ === Object.__proto__.__proto__);
console.log(Object.__proto__.__proto__.__proto__ === null);
对象xm
原型链为:
xm --> Student.__proto__ --> Object.__proto__ --> null
2、构造函数
除了直接用{ ... }创建一个对象外,JavaScript还可以用一种构造函数的方法来创建对象。它的用法是,先定义一个构造函数,再用关键字new来调用这个函数,并返回一个对象。构造函数和普通函数并无差别。如果不写new,它就是一个普通函数,它返回undefined。但是,如果写了new,它就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this,也就是说,不需要在最后写return this。
function Student(name) {
this.name = name;
this.hello = function () {
console.log('Hello, ' + this.name);
};
}
var jack = new Student('jack');
console.log(jack.name); //jack
jack.hello(); //Hello, jack
console.log(jack.__proto__ === Student.prototype)
console.log(Student.prototype.__proto__ === Object.prototype)
console.log(Object.prototype.__proto__ === null)
对象jack
的原型链是:
jack --> Student.prototype --> Object.prototype --> null
用new Student()创建的对象还从原型上获得了一个constructor属性,它指向函数Student本身:
console.log(jack.constructor === Student.prototype.constructor) // true
console.log(Student.prototype.constructor === Student) // true
console.log(Object.getPrototypeOf(jack) === Student.prototype) // true
console.log(jack instanceof Student) // true
### 3、__proto__和prototype **JavaScript 对每个创建的对象都会设置一个原型,指向它的原型对象。**在JS中万物皆对象。字符串String是对象,数组([])是对象,对象({})是对象,函数方法(Function)是对象。下面分别看下一般对象 和 函数对象 的原型连。 String对象的原型链: 和之前的对象`xm`类似字符串对象`str`也只有`__proto__`属性,没有`prototype`属性。 `str`的原型链是: **`str --> String.prototype --> Object.prototype --> null`** ``` var str = 'aaaaaaaa'
//对象的__proto__ 和 prototype
console.log(str.proto) //[String: '']
console.log(str.prototype) //undefined
console.log(typeof(str.proto)) //object
// 原型链
console.log(str.proto === String.prototype) //true
console.log(String.prototype.proto === Object.prototype) //true
console.log(Object.prototype.proto === null) //true
// 构造函数
console.log(str.constructor === String.prototype.constructor) //true
console.log(String.prototype.constructor === String) //true
console.log(Object.prototype.constructor === Object) //true
// 类型
console.log(typeof(String.prototype)) //object
console.log(typeof(String.prototype.proto)) //object
console.log(typeof(Object.prototype)) //object
console.log(typeof(Object.prototype.proto)) //object
函数对象的原型链,函数对象不仅有`__proto__`,还有属性`prototype`。
那么`Function()`,`Object()`和前面的`String()`是函数对象,因为它们有`prototype`属性。
通过`new Foo()`构建的对象`foo_object`的原型链是:
**`foo_object --> Foo.prototype --> Object.prototype --> null`**
函数`Foo()`也是一个对象,它的原型链是:
**`Foo() --> Function.prototype --> Object.prototype --> null`**
function Foo() {
return 0;
}
var foo_object = new Foo();
// 函数对象的__proto__ 和 prototype
console.log(Foo.proto) //[Function]
console.log(Foo.prototype) //Foo {}
console.log(typeof(Foo.proto)) //function
console.log(typeof(Foo.prototype)) //object
// 原型链
console.log(foo_object.proto === Foo.prototype); // true
console.log(Foo.prototype.proto === Object.prototype); // true
console.log(Object.prototype.proto === null) // true
console.log(Foo.proto === Function.prototype); // true
console.log(Function.prototype.proto === Object.prototype); // true
console.log(Object.prototype.proto === null) // true
// 构造函数
console.log(Foo.constructor === Function.prototype.constructor) //true
console.log(Function.prototype.constructor === Function) //true
console.log(Object.prototype.constructor === Object) //true
// 类型
console.log(typeof(Function.prototype)) //function
console.log(typeof(Function.prototype.proto)) //object
console.log(typeof(Object.prototype)) //object
console.log(typeof(Object.prototype.proto)) //object
**1).所有对象有属性`__proto__`,指向对象的原型对象。
2).函数对象除了有属性`__proto__`,还有属性`prototype`,`prototype`属性指向(构造)函数对象共享的属性和方法(即一个由此构造函数构造而来的对象可以继承prototype指向的属性及方法),`prototype`属性使您有能力向对象添加属性和方法。其中有一个`constructor`属性指向函数对象本身(`String.prototype.constructor === String`,`Function.prototype.constructor === Function`)。**
![](http://images2015.cnblogs.com/blog/490092/201703/490092-20170309225944734-1481442121.jpg)
展示下如何使用 prototype 属性来向对象添加属性:
1)通过`Student.prototype`的方式向函数对象添加了`age`和`hello()`属性,后续构建的对象`jack`和`sara`都继承了所增属性。
2)`hello()`属性分别在`sara`对象构建之前和`jack`对象构建之后添加的,但继承得到的`hello()`是一样的。那么在JS中一个已构建的对象,还可以通过其原型对象“不着痕迹”的扩展属性。
function Student(name) {
this.name = name;
}
Student.prototype.age = 23;
var jack = new Student('jack');
Student.prototype.hello = function () {
console.log('Hello, ' + this.name + '!');
};
var sara = new Student('sara')
console.log(jack.age); //23
jack.hello(); //Hello, jack!
console.log(sara.age) //23
sara.hello() //Hello, sara!
console.log(sara.hello === jack.hello) //true
<br/>
### 4、原型继承
JavaScript由于采用原型继承,我们无法直接扩展一个Class,因为不存在Class这种类型。
`Student`的构造函数
function Student(props) {
this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function () {
console.log('Hello, ' + this.name + '!');
}
如果要基于`Student`扩展出`PrimaryStudent`,可以先定义出`PrimaryStudent`:
function PrimaryStudent(props) {
// 调用Student构造函数,绑定this变量:
Student.call(this, props);
this.grade = props.grade || 1;
}
但是,调用了`Student`构造函数不等于继承了`Student`,`PrimaryStudent`创建的对象的原型链还是:
`new PrimaryStudent() --> PrimaryStudent.prototype --> Object.prototype --> null`
要想办法把原型链修改为:
**`new PrimaryStudent() --> PrimaryStudent.prototype --> Student.prototype --> Object.prototype --> null`**
这样,原型链对了,继承关系就对了。新的基于`PrimaryStudent`创建的对象不但能调用`PrimaryStudent.prototype`定义的方法,也可以调用`Student.prototype`定义的方法。
可借助一个中间对象来实现正确的原型链,中间对象可以用一个空函数F来实现,原型指向`Student.prototype`。代码如下:
// Student构造函数:
function Student(props) {
this.name = props.name || 'Unnamed';
}
Student.prototype.hello = function () {
console.log('Hello, ' + this.name + '!');
}
// PrimaryStudent构造函数:
function PrimaryStudent(props) {
Student.call(this, props);
this.grade = props.grade || 1;
}
// 中间对象-空函数F():
function F() {
}
// step1:把F的原型指向Student.prototype:
F.prototype = Student.prototype;
// step2:把PrimaryStudent的原型指向一个新的F对象,F对象的原型正好指向Student.prototype:
PrimaryStudent.prototype = new F(); //PrimaryStudent.prototype.proto === F.prototype === Student.prototype
// step3:把PrimaryStudent原型的构造函数修复为PrimaryStudent:
PrimaryStudent.prototype.constructor = PrimaryStudent;
// 继续在PrimaryStudent原型(就是new F()对象)上定义方法:
PrimaryStudent.prototype.getGrade = function () {
return this.grade;
};
// 创建xiaoming:
var xiaoming = new PrimaryStudent({
name: '小明',
grade: 2
});
xiaoming.name; // '小明'
xiaoming.grade; // 2
// 验证原型链:
console.log(xiaoming.proto === PrimaryStudent.prototype); // true
console.log(PrimaryStudent.prototype.proto === Student.prototype); // true
console.log(Student.prototype.proto === Object.prototype); // true
// 验证继承关系:
console.log(xiaoming instanceof PrimaryStudent); // true
console.log(xiaoming instanceof Student); // true
<br/>
### 5、class继承
新的关键字class从ES6开始正式被引入到JavaScript中。
如果用新的`class`关键字来编写Student,而且可以直接通过`extends`来实现继承,原型继承的中间对象,原型对象的构造函数等都不需要考虑了。
使用`class`定义的对象如`Student`还是函数对象,`class`的目的就是让定义类更简单。`class`的定义包含了构造函数`constructor`和定义在原型对象上的函数`hello()`(注意没有function关键字),这样就避免了`Student.prototype.hello = function () {...}`这样分散的代码。
// class关键
class Student {
constructor(name) {
this.name = name;
}
hello() {
console.log('Hello, ' + this.name + '!');
}
}
console.log(typeof(Student)); //function
var xiaoming = new Student('小明');
xiaoming.hello(); // Hello, 小明!
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super调用父类的构造方法!
this.grade = grade;
}
myGrade() {
console.log('I am at grade ' + this.grade);
}
}
var xiaohong = new PrimaryStudent('小红', 24)
xiaohong.hello() // Hello, 小红!
xiaohong.myGrade() // I am at grade 24
ES6引入的class和原有的JavaScript原型继承有什么区别呢?实际上它们没有任何区别,class的作用就是让JavaScript引擎去实现原来需要我们自己编写的原型链代码。简而言之,用class的好处就是极大地简化了原型链代码。