1、小思考 我记得初学js时,最难懂的概念就是js的原型,而且这个概念在笔试面试中常常提到,因此今天我们把这个概念拿出来,好好聊一聊。
在仔细讲解之前,我们先来看一道题,这道题来自JavaScript高级程序设计中原型链那一节:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function SuperType ( ) {
this .property = true ;
}
SuperType.prototype.getSuperValue = function ( ) {
return this .property;
};
function SubType ( ) {
this .subproperty = false ;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function ( ) {
return this .subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());
可以猜一猜最后alert出的结果是什么?
先思考一下再看下面的内容。
2、知识点 如果上面的题没解出来,也不要灰心丧气,因为有可能有的人解出来的结果也不一定对。要想弄明白原型等一系列概念,其实只需要记住这几个知识点。
2.1 先弄懂什么是prototype以及proto proto :任何一个对象Object都有proto ,它是每一个对象的私有属性,是天生自带的。 prototype:不是任何对象都有prototype,只有构造函数有prototype,是后天赋予的。
2.2 什么是原型链查找 其实只需要记住一句话:调用一个对象的属性或方法,一但这个对象中没有,就去这个对象的proto 中查找。这个对象的proto 指向自己构造函数的prototype属性 然后我们来看一张图: 再来看一个例子:1
2
3
4
5
6
7
8
var Person = function ( ) {
this .sleep = 'Zzzzz...'
}
Person.prototype.sayHello = function ( ) {
console .log('hello world' );
}
var zhangsan = new Person();
zhangsan.sayHello();
其实zhangsan这个对象下面只有一个sleep属性,是没有sayHello方法的。 但是通过原型链查找会查zhangsan.proto 也就是查找它的构造函数的Person.prototype,Person.prototype下有sayHello这个方法,所以会在控制台输出hello world 所以啦,万变不离其宗!
3、几种继承方式 除了最新的ES6外,js其实是没有继承和类的概念的,因此想要达到js的继承就要通过模拟的方式。这里将主要介绍三种继承的方式:纯原型链继承,借用构造函数继承,组合继承:
3.1 纯原型链继承 多说无益,来个简单暴力直接的方式,直接上代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Father ( ) {
this .likeFood= ['牛排' ,'饺子' ,'啤酒' ,'可乐' ]
}
Father.prototype.saylikeFood = function ( ) {
console .log(this .likeFood);
};
function Son ( ) {
}
Son.prototype = new Father();
var zhangsan = new Son();
zhangsan.likeFood.push('西瓜' );
zhangsan.saylikeFood();
Ok,这就是纯原型链的继承方式,其实说白了就是把继承的对象的prototype等于继承自构造函数的实例。 但是这样的方式有问题。接着上面的代码的后面我们再写:1
2
var lisi = new Son();
lisi.saylikeFood();
看出问题了吧,zhangsan直接修改了其构造函数的likeFood, 导致我们再实例的对象也收到了修改的影响, 因此这种继承方式有缺陷
3.2 借用构造函数继承 还是多说无益,我们直接来看例子,上代码:1
2
3
4
5
6
7
8
9
10
11
12
13
function Father (name ) {
this .name = name;
this .sayName = function ( ) {
console .log(this .name);
}
}
function Son (name, age ) {
Father.call(this , name);
this .age = age;
}
var zhangsan = new Son('zhangsan' , 17 );
这种继承方式并没有利用到原型以及原型链的概念,它主要利用call的特性,call的第一个参数传入this,后面的参数传入函数所需的参数。 这种方式归根结底其实就是在实例一个对象的时候,向这个对象的上面添加所需的属性和方法。 不信的话可以输出zhangsan看一下
但是其实这种方式也有问题,什么问题呢? 按照这种方式,每次new一个对象,就是实例化一个对象,都会向这个对象身上添加一堆属性和方法。 添加属性是没问题的,但是每次在对象身上添加的方法,这个函数就要重写一次。 函数不能进行复用,这就是最大的问题!
3.3 组合继承 这次要多说两句,组合继承其实分别是拥有以上两种方法的优点,同时也规避了以上两种方法的缺点。这种方法的应用是最广泛的,是最普遍的,举个栗子来看一下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Father (name ) {
this .name = name;
}
Farther.prototype.sayName = function ( ) {
console .log(this .name);
}
function Son (name, age ) {
Farther.call(this , name);
this .age = age;
}
Son.prototype = new Father();
Son.constructor = Son;
Son.prototype.sayage = function ( ) {
console .log(this .age);
}
var zhangsan = new Son();
来看一下,这种方式不错吧! 其实除了组合继承外,还有两种继承方式:原型式继承和寄生式继承.
(注:后续会详细分享原型式继承与寄生式继承)