javascript继承简单理解
最近回看一些面试题,然后翻翻书,把一些很老的东西整理一下。
先讲个段子,有个学长去某公司面试,人家问你对js的面向对象编程怎么看。学长很机智的说了一句:es5实现的不好,我觉得es6才是王道。然后面试官点了点头,似乎没法往下问了。
javascript的面向对象的知识一直只是在书上看,也没真正的去写过,今天就自己去实现一下。
javascript里遇到的东西大部分都是对象(object),Array和Object虽然不一样,但是区别不大。我们可以用new后加构造函数可以去实例化一个对象。或者对象字面量,数组[],对象{}。
1 2 | var a = new Array(); var b = new Object(); |
这个new在其他语言里面的确是实例化一个对象的意思,但是在javascript好像不一样。
###new大致流程
在javascript里面,new的过程大概是这样的
1.首先创建个Object
2.修改这个对象的proto,使其指向构造函数的prototype
3.将这个对象交给构造函数的this,调用构造函数
4.如果构造函数没有return,那么返回这个对象。否则构造函数返回return语句后面的内容
这个过程我们也可以用代码去实现
1 2 3 4 5 6 7 8 | Function.prototype._new = function(){ var newObj; var resultObj; newObj = {}; newObj.__proto__ = this.prototype;//修改__proto__,指向构造函数的prototype resultObj = this.apply(newObj, arguments); return (typeof resultObj === 'object' && resultObj) || newObj; } |
这样就可以用
1 | var newFunc = Function._new(); |
来实现一个function的定义了
在一个对象的
prototype
里面,还有一个constructor
属性,指向构造函数本身,如果我们想通过原型做继承,把原型指向parent,这样会造成constructor不正确。所以在利用
prototype
做继承的时候,一定要记得把constructor
修改回来。代码如下:
1 2 3 4 5 6 | function Parent(){ } function Children(){ } Children.prototype = Parent(); Children.prototype.constructor = Children; |
这种继承方式是最简单的,但是问题也是很多的。
在天镶博客里看到了Es5里新增了
Object.create
的方法,用于继承,其实就是封装了刚才的继承过程用法如下:1 2 3 4 5 6 7 8 | function Parent(){ } function Children(){ } Children.create(Parent.prototype); Children.prototype.constructor = Children; var obj = new Children(); |
这种继承的方法和之前的区别是,我们没有实例化Parent对象,而是直接把Parent.prototype拷贝到Children.prototype里了。当然这种继承方式有兼容性缺陷,但是只有父类的prototype上的属性被继承了,而且是继承自一个父类实例。
拷贝实现继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /*定义一个人类*/ function Person(name,age) { this.name=name; this.age=age; } /*定义一个学生类*/ function Student(name,age,grade) { Person.apply(this,arguments); this.grade=grade; } //创建一个学生类 Student.prototype = Person.prototype; var student=new Student("qian",21,"一年级"); //测试 console.log("name:"+student.name+"\n"+"age:"+student.age+"\n"+"grade:"+student.grade); //大家可以看到测试结果name:qian age:21 grade:一年级 //学生类里面我没有给name和age属性赋值啊,为什么又存在这两个属性的值呢,这个就是apply的神奇之处. |
这里有个例子,定义一个person类和Student类,通过apply用当前Student的this替换掉了Persion类的this并执行,同时把arguments传过去。这样就可以把Person的属性都拷贝过来。从而实现属性继承。
把属性拷贝过来的继承,然后再通过原型共用就可以把原型上的方法继承过来了
这样的继承的特点就是:
-子类的属性会把父类覆盖,子类可以修改父类的属性,并且不会像之前直接用修改原型实现的继承导致修改父类属性会造成其他的对象同时被修改。
-但是子类和父类共享prototype,这样就不能随意修改prototype上的东西,否则其他的对象也会受到影响。
为了解决第二点的麻烦,所以要让子类和父类不共用原型。
浅拷贝不共用原型继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*定义一个人类*/
function Person(name,age)
{
this.name=name;
this.age=age;
}
/*定义一个学生类*/
function Student(name,age,grade)
{
Person.apply(this,arguments);
this.grade=grade;
}
Student.prototype = new Person();
Student.prototype.constructor = Student;
//创建一个学生类
var student=new Student("qian",21,"一年级");
//测试
console.log("name:"+student.name+"\n"+"age:"+student.age+"\n"+"grade:"+student.grade);
这样虽然不共用原型了,但是会有很多个Parent用来继承,有点浪费了。
所以我们还有一种简单粗暴的方法,就是直接把所有的属性和方法直接拷贝一遍
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*定义一个人类*/
function Person(name,age)
{
this.name=name;
this.age=age;
}
/*定义一个学生类*/
function Student(name,age,grade)
{
var parentIst = new Person();
var item;
for(item in parentIst) {
this[item] = parentIst[item];
}
this.grade=grade;
}
//创建一个学生类
var student=new Student("qian",21,"一年级");
//测试
console.log("name:"+student.name+"\n"+"age:"+student.age+"\n"+"grade:"+student.grade);
这样就复制过来了。但是还是有问题。。//尼玛怎么这么多问题
问题在哪里,这种拷贝属于所谓的“浅拷贝”,如果父类的属性是引用类型,最终实现的只不过是新增一个引用而已,这样在子类里进行修改引用类型会造成所有的属性发生改变。那怎么办呢?
深拷贝
深拷贝其实不难,就是繁琐了一点,递归使用浅拷贝
实现的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function deepCopy(p,c){
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
/*定义一个人类*/
function Person(name,age)
{
this.name=name;
this.age=age;
}
/*定义一个学生类*/
function Student(name,age,grade)
{
var parentIst = new Person();
var item = deepCopy(parentlst);
for(item in parentIst) {
this[item] = parentIst[item];
}
this.grade=grade;
}
//创建一个学生类
var student=new Student("qian",21,"一年级");
//测试
console.log("name:"+student.name+"\n"+"age:"+student.age+"\n"+"grade:"+student.grade);
这样就直接实现了深拷贝