JavaScript原型继承

JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身。

在传统的 OOP 中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在 JavaScript 中并不如此复制——而是在对象实例和它的构造器之间建立一个链接(它是proto属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。–MDN

实例

说一下自己的理解,我们在JavaScript创建的函数都有共同的属性叫prototype,它长这样

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
function test (){}
console.log(test.prototype);

Object
constructor: ƒ test()
arguments: null
caller: null
length: 0
name: "test"
prototype: {constructor: ƒ}
__proto__: ƒ ()
[[FunctionLocation]]: VM123:1
[[Scopes]]: Scopes[2]
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()

向原型中添加属性

1
test.prototype.foo = 'bar';

理解原型对象

原型链中的方法和属性没有被复制到其他对象

官方并没有规定方法可以直接访问对象原型,原型链的’连接’被定义在内部属性中,在JavaScript中用[[prototype]]表示,在使用chrome和firefox时,提供了__proto__这个属性可以访问原型对象

需要注意的是__proto__prototype的区别,前者可以访问对象的原型对象,后者包含了一个对象,可以在里面定义被继承的成员,prototype是构造器的专属属性

而前面说的Object.create(),就是将参数里的对象作为原型对象返回一个新的对象

constructor

每个实例的对象还会从原型中继承constructor属性,该属性就是这个对象的构造函数

1
2
3
4
let o1 = new test();
//下面两种方法都可以得到构造函数
o1.constructor;
o1.__proto__.constructor;

用途1,利用这个构造函数创建对象

1
let o2 = new o1.constructor();

用途2,得到构造函数的各种信息

1
2
3
4
//名字
o1.constructor.name;
//参数长度
o1.constructor.length;

修改原型

因为原型链的特性,所以增加构造函数的原型属性时,使用构造函数实例化的对象也可以访问到这个属性,如

1
2
3
4
Person.prototype.newAtrribute = function(){
alert('hello'+this.name[0]);
}
person1.newAtrribute();

注意上面的this是在函数里面的,在使用时就会转换为对象实例

常见的对象定义模式就是,构造器(函数体)里定义属性,prototype属性里定义方法

继承

原型继承

Java的继承是规定某个类继承自某个类,而JavaScript的继承方式有点特别,被称为 原型式继承 —— prototypal inheritance

先创建一个Person类

1
2
3
4
5
6
7
8
9
10
11
function Person(first, last, age, gender) {
this.name = {
first,
last
};
this.age = age;
this.gender = gender;
};
Person.prototype.greeting = function() {
alert('Hi! I\'m ' + this.name.first + '.');
};

此时的原型链是

1
new Person() ----> Person.prototype ----> Object.prototype ----> null

然后需要创建一个Teacher类,它需要继承Person类,并添加一个新的属性subject,重写一个方法greeting,于是有

1
2
3
4
5
function Teacher(first, last, age, gender, subject) {
Person.call(this, first, last, age, gender);

this.subject = subject;
}

call()表示为Teacher调用了Person构造方法,然后第一个参数绑定this变量

如果继承的是无参函数,那么只需要传递一个this即可

但是它的原型链并没有指向Person,而是

1
new Teacher() ----> Teacher.prototype ----> Object.prototype ----> null

我们需要的是

1
new Teacher() ----> Teacher.prototype ----> Person.prototype ----> Object.prototype ----> null

原型与构造器引用

上面Teacher类还不完整,有两个问题

1.Teacher的原型没有继承自Person

解决方法,使用create(),得到一个原型是Person的中间对象,并将Teacher的原型指向这个对象(即Teacher的原型对象指向了Person的原型对象)

1
Teacher.prototype = Object.create(Person.prototype);

2.因为使用了create(),所以Teacher的prototype的constructor指向Person,它应该指向Teacher本身,所以

1
2
Teacher.prototype.constructor = Teacher;
//再次强调,Teacher后面加括号代表调用

注:每一个函数对象(Function)都有一个prototype属性,并且只有函数对象有prototype属性,因为prototype本身就是定义在Function对象下的属性。当我们输入类似var person1=new Person(...)来构造对象时,JavaScript实际上参考的是Person.prototype指向的对象来生成person1。另一方面,Person()函数是Person.prototype的构造函数,也就是说Person===Person.prototype.constructor(不信的话可以试试)。–MDN

最后,重写greeting函数

1
2
3
4
5
6
7
8
9
10
11
12
13
Teacher.prototype.greeting = function() {
var prefix;

if(this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
prefix = 'Mr.';
} else if(this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
prefix = 'Mrs.';
} else {
prefix = 'Mx.';
}

alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.');
};

注: 考虑到JavaScript的工作方式,由于原型链等特性的存在,在不同对象之间功能的共享通常被叫做 委托 - 特殊的对象将功能委托给通用的对象类型完成。这也许比将其称之为继承更为贴切,因为“被继承”了的功能并没有被拷贝到正在“进行继承”的对象中,相反它仍存在于通用的对象中。–MDN

class继承

ES6引入了calss,简化了原型继承的代码量(继承原理不变)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greeting() {
alert('Hi! I\'m ' + this.name.first + '.');
}
}

class Student extends Person{
constructor(name, age, status){
super(name, age);
this.status = status;
}

doHomework(){
alert('我在做作业');
}
}
s1 = new Student('manu',19,'learn')

注意兼容性再考虑是否使用class

作者

manu

发布于

2020-04-19

更新于

2023-01-06

许可协议

# 相关文章
  1.JavaScript对象
# 推荐文章
  1.ALTER
  2.数据库笔记
  3.微信小程序笔记
  4.动态调试-OD
  5.winPE结构
  6.git指令

:D 一言句子获取中...