javascript 知识点整理 04 原型 继承

这部分内容的整理都来自于《js高级程序设计》

面向对象

创建对象(构造函数 原型模式)

继承(原型 原型链)

面向对象

理解对象

  • 对象就是名值对,一个名称对应一个值(可以是基本值、对象、函数)
  • 通过for-in枚举的属性顺序是不可预测的

属性类型

数据属性

  • configurable
  • enumerable
  • writable
  • value

访问器属性

  • configurable
  • enumerable
  • get
  • set

定义属性

Object.defineProperty

Object.defineProperties

获取属性

Object.getOwnPropertyDescriptor


创建对象

工厂模式

1
2
3
4
5
6
7
8
function Person (name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
return obj;
}
var person1 = Person("lily", 25)


构造器模式

1
2
3
4
5
6
7
8
9
function Person (name, age) {
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name);
}
}
var person1 = new Person("lily", 25)


原型模式

理解原型

  • 创建构造函数时,构造函数内会有一个prototype属性指向构造函数的原型对象

  • 原型对象内会有一个constructor属性,该属性指向prototype的保有者

  • 使用该构造函数创建的实例会继承来自原型对象的属性,同时实例会有一个[[prototype]]内部属性指向构造函数的原型对象(不是构造函数)。部分浏览器使用__proto__来表示这个内部属性

  • 实例无法修改原型的属性


属性查找

  • 实例会先在自身查找同名属性和方法,查找不到的会通过[[prototype]]指针去原型上查找
  • 如果原型被重写,则原型重写前的实例会和之前的原型断开联系;重写原型后创建的实例才会指向现在的原型


属性访问

  • hasOwnProperty 查找实例自身的属性(不是原型上的)

  • in 查找对象的属性,存在则返回true

判断原型上是否有该属性

1
2
3
function hasPrototypeProperty (object, prop) {
return !object.hasOwnProperty(prop) && (prop in object);
}
  • for-in 返回所有可以枚举的属性

  • Object.keys ES5 返回所有可枚举的属性

  • Object.getOwnPropertyName


重写原型

  • 为了方便给原型写入属性,可以直接用对象字面量重写原型

  • 重写原型,默认的consturctor会被修改,指向Object对象,因为constructor指向的是构造函数,此刻用对象字面量重写原型,构造器已经不是之前的构造函数了

  • 重写原型后,在重写之前声明的实例,都无法访问到从重写后的属性
    因为实例和最初的原型之间的联系已经被断掉了。实例的[[prototype]]指向的是最初的构造函数的原型对象,而不是构造函数,所以重写的新的原型和之前构造函数的原型对象是不一样的


原型中的一些问题

如果原型属性的值是一个引用类型,那么实例中查询到的原型上的这个属性的值只是一个指针,对该属性的操作都将作用于所用相同构造函数构建的实例上。如果希望每个实例的属性都将是不同的,那么这种方式会造成一些问题(除非咱就是想让他共享)


构造函数模式 + 原型模式

  • 通过构造函数定义实例属性,同时可以传入参数
  • 通过原型定义一些共享的属性和方法
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;
this.friends = ["Kitty"];
}
Person.prototype.getName = function() {
console.log(this.name);
}
var person1 = new Person("Tom", 21),
person2 = new Person("John", 22);
person1.getName();
person1.friends.push("Dick");
console.log(person1.friends);
person2.getName();
console.log(person2.friends);
console.log(person1.getName === person2.getName);
console.log(person1.friends === person2.friends);


动态原型模式


寄生构造函数模式

  • 用的方法与工厂模式差不多,只不过最后用的是new来创建了对象
1
2
3
4
5
6
7
8
9
10
11
function Person(name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.sayName = function() {
console.log(this.name);
}
return obj;
}
var person1 = new Person("lily", 25)


稳妥构造函数模式

  • 不使用this和new
1
2
3
4
5
6
7
8
9
10
function Person(name, age) {
var obj = new Object();
obj.sayName = function() {
console.log(name);
}
return obj;
}
var person1 = Person("lily", 25)


继承

原型链继承(一般不单独使用)

理解原型链

  • 个人认为,原型链的核心就是:让一个构造函数的原型对象,成为另一个构造函数的实例对象

  • 这样子类构造函数的原型对象就会拥有一个[[prototype]]的内部属性,指向另一个构造函数的原型对象,并且继承这个构造函数和原型对象的所有属性和方法

  • 让“原型等于实例”这个过程不断向后延伸,就会形成一个链式结构,称之为原型链,能够让子类继承后面的构造函数的实例属性、原型方法

作用域链:标识符一级级的检索
原型链:一级级的继承属性和方法


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Super() {
this.superProp = true;
}
Super.prototype.getSuperProp = function() {
console.log(this.superProp);
}
function Sub() {
this.subProp = false;
}
Sub.prototype = new Super();
// 将Sub的原型实例化,这样Sub的原型也就以Super的原型为原型了
// 也可以使用Object.create()的方式为Sub指定一个原型
// Sub.prototype = Object.create(Super.prototype);
Sub.prototype.getSubProp = function() {
console.log(this.subProp);
}
var instance = new Sub();
instance.getSuperProp();
instance.getSubProp();

原型链继承的问题

  • 实例的原型现在也是一个实例,所以原型继承了父类构造器的实例属性。如果这个属性是引用类型,那所有子类实例都会共享这个属性而不是各自创建一个副本


借用构造函数(一般不单独使用)

  • call apply
  • 单纯借用构造函数无法使用构造函数原型上的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Super() {
this.superProp = true;
}
Super.prototype.getSuperProp = function() {
console.log(this.superProp);
}
function Sub() {
Super.call(this);
this.subProp = false;
}
var instance = new Sub();
console.log(instance.superProp); //ture
console.log(instance.subProp); //false
instance.getSuperProp(); //getSuperProp undefined


组合继承(原型链+构造函数继承)

  • 在构造函数上定义属性,在原型上定义方法
  • call apply继承属性,用原型链继承方法
  • 构造器定义的引用类型的实例属性,每次构造都会生一个副本,而在原型上定义的引用类型的属性是共享的
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
38
function Super(name) {
//定义属性
this.name = name ;
this.color = ["red", "yellow", "blue"];
}
Super.prototype.getSuperName = function() {
//定义方法
console.log(this.name);
}
function Sub(name,age) {
//继承属性,调用Sub执行Super的语句
Super.call(this,name);
this.age = age;
}
//继承方法
Sub.prototype = new Super();
Sub.prototype.constructor = Sub;//原型被重写,重新定义constructor指针
//创建子类方法
Sub.prototype.getSubAge = function() {
console.log(this.age);
}
//创建实例
var instance1 = new Sub("Amy",20),
instance2 = new Sub("Bob",21);
instance1.color.push("green");
console.log(instance1.color);
instance1.getSuperName();
instance1.getSubAge();
console.log(instance2.color);
instance2.getSuperName();
instance2.getSubAge();


原型式继承

  • 不创建构造函数,只是让一个对象与另一个对象保持相似
  • 相当于进行了一次浅拷贝,让内部构造函数的原型直接等于传入的对象,类似于原型链继承
1
2
3
4
5
function object(o) {
function Func() {};
Func.prototype = o;
return new Func();
}
  • Object.create()


寄生式继承

  • 类似构造函数继承,给实例对象添加方法,但是难以实现复用
1
2
3
4
5
6
7
function createFunc(o) {
var clone = obejct(o);
clone.someMethod = function() {
statement
}
return clone
}


寄生式原型继承

  • 原型链+构造函数继承会导致两次调用父类构造函数
  • 寄生式原型继承在于简化这一过程,只是一次调用父类构造函数

总结

创建对象

See the Pen 面向对象:创建对象 by ConanTvos (@lcc19941214) on CodePen.

继承的几种方法

See the Pen 面向对象:继承的几种方式 by ConanTvos (@lcc19941214) on CodePen.