这部分内容的整理都来自于《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]]
指针去原型上查找
- 如果原型被重写,则原型重写前的实例会和之前的原型断开联系;重写原型后创建的实例才会指向现在的原型
属性访问
判断原型上是否有该属性
1 2 3
| function hasPrototypeProperty (object, prop) { return !object.hasOwnProperty(prop) && (prop in object); }
|
重写原型
为了方便给原型写入属性,可以直接用对象字面量重写原型
重写原型,默认的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)
|
稳妥构造函数模式
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.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); console.log(instance.subProp); instance.getSuperProp();
|
组合继承(原型链+构造函数继承)
- 在构造函数上定义属性,在原型上定义方法
- 用
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) { Super.call(this,name); this.age = age; } Sub.prototype = new Super(); Sub.prototype.constructor = Sub; 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(); }
|
寄生式继承
- 类似构造函数继承,给实例对象添加方法,但是难以实现复用
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.