js实现继承

js实现继承

一、尝试实现原型继承

1
2
3
4
5
6
7
8
9
10
11
12
function Person(){

}
Person.prototype.dance = function(){};

function Ninja(){}
Ninja.prototype = new Person();

const ninja = new Ninja();
console.log(ninja instanceof Ninja); //true
console.log(ninja instanceof Person) //true
console.log(ninja instanceof Object) //true

当定义一个Person函数的时候,同时也创建了一个Person原型,该原型通过其construtor属性引用函数的本身。同时下面也扩展了Person原型,加入了相关的方法。后面我们也定义了一个Ninja函数,并且该函数原型也有一个constructor属性引用函数本身。为了实现继承,将Ninja的原型赋为Person的实例。之后每创建一个Ninja对象的时候,新创建的Ninja对象将设置为Ninja的原型属性所指向的对象,即Person。

当访问Ninja对象的dance方法时,js首先会查找Ninja对象本身。没有就会找他的原型即Person。Person不具有dance方法就会再接着查找Person对象的原型,这时候找到了dance方法。执行instance操作符的时候,可以判定函数是否继承原型链上的对象功能。

其实实现继承还可以直接使用Person的原型对象作为Ninja的原型,如Ninja.prototype = Person.prototype。这样做会导致Person原型上发生的所有变化都被同步到Ninja原型上(Person原型和Ninja原型是同一个对象),会有一定的副作用。

constructor属性的问题

​ 仔细观察可以发现,上述代码中,会存在丢失Ninja与Ninja初始原型的之间的关联,这是一个问题。如果利用constructor检查一个对象是否由某一个函数创建的时候,去检查Ninja会得到的是Person。显然这个答案是错误的。

配置对象的属性

在js中,对象是通过属性描述的,可以通过配置一下关键字:

  • configurable——如果设置为true,则可以修改或删除属性。
  • enumerate——如果如果设置为true,则可在for-in循环对象属性出现
  • value——指定其属性的值
  • writable——如果为true,可以通过赋值语句修改属性值
  • get——定义getter函数,当访问属性时发生调用,不能与value和writable同时使用。
  • set——定义setter函数,当对属性赋值的时候调用,不能与value和writable同时使用。

当然,可以使用内置的Object.defineProperty方法属性定义一个属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var ninja = {};
ninja.name = "haha";
ninja.sex = 'man';

Object.defineProperty(ninja,'test',{
configurable:false,
writable:true,
value:false,
enumerable:false
});

console.log('test' in ninja) //true
for(let m in ninja){
console.log(m) //name,sex
}

利用上面的方法,就可以解决constructor被覆盖的问题,即通过Object.defineProperty在Ninja.defineProperty上定义一个constructor属性。

instanceof操作符

在上面的例子中,虽然Ninja的原型是Person,但是通过instanceof还是可以发现ninja instanceof Ninja的返回值是true,说明ninja也是Ninja的实例。在这个过程中,javascript首先检查Ninja函数的原型——new Person()对象,检查他是否存在ninja的原型链上。由于new Person()对象确实是ninja的原型,所以表达式为true。实际上,ninja实例的原型链是由new Person()对象和Person的原型组成的。

ES6语法中使用class关键字:

1
2
3
4
5
6
7
8
9
10
11
class test{
constructor(name){
this.name = name;
}
swing(){
return true;
}
}

var haha = new test('tom');
console.log(haha.swing())

从上面的代码可以看出,ES6语法支持使用类似其他面向对象语言的方式用class来定义一个类。但实际上,class只是个语法糖,底层仍是基于原型的实现。上述定义类可转换成下面的ES5代码:

1
2
3
4
5
6
function Ninja(name){
this.name = name;
}
Ninja.prototype.swing = function(){
return true;
}

同时在ES6中还支持定义像Java一样的静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class test{
constructor(name){
this.name = name;
}
swing(){
return true;
}
static sum(x,y){
return x + y;
}
}

console.log(test.sum(1,2))
var haha = new test('tom');
console.log(haha.swing())

实现继承

js也可以像java一样利用extends来实现继承:

1
2


在student构造器中通过关键字super调用基类Person的构造函数。

0%