一个月前写的原型与原型链。那时还只是刚明白了__proto__与prototype之间的关系。
因此那时对于继承的认知是不够的。所以对于继承来说,我还需要继续总结。(在过了一个月后。。。)
es6之前的继承
- 首先是
prototype 和 __proto__
. 我发现即使我记住了原型链图。 但是我发现当使用Object.getPrototypeOf()、Object.setPrototypeOf(). 这些方法时,我还是捋不清其中的联系。因此还是要具体了解这两个东西是干什么用的。
- proto 。每个JS对象
一定对应一个原型对象
(每个对象上都有一个__proto__属性)。 对象的__proto__的值就是它所对应的原型对象。 (称为该对象的原型
)- 如下述。我们创建了一个obj对象,其__proto__的值是它对应的原型(Object.prototype) . 因为图中为true。
- 引用:
引用也就是而可以通过__proto__属性访问到对象的prototype
- 其实ECMAScript 规范prototype应是个
隐式引用(只能间接访问)
:- Object.getPrototypeOf(obj) 间接访问指定对象的 prototype 对象
- Object.setPrototypeOf(obj, anotherObj) 间接设置指定对象的 prototype 对象
- 但是部分浏览器。 却可以通过__proto__ 去直接操作prototype. 所以才有了__proto__这个概念 。(因此不推荐我们却使用它去进行操作。而是用于理解原型链的模型。)
const obj = {
name: 'fjh'
}
console.log(obj)
console.log(obj.prototype) //undefined . 因为obj并不是个函数对象。所以其也没有能与上述职能的prototype对象的关联。
console.log(obj.__proto__ === Object.prototype)
- prototype
- 定义为:给其他对象
提供共享属性
的对象。 - 其本身不过是个对象。只是承担了提供属性功能的职责。它就成了该对象的prototype. 对象间形成了
某种关联
。 - 因此并不是每个对象都有prototype属性。只有函数对象才有该属性。且该对象内部有个
constructor
属性又指向了它的构造函数(对应的函数对象)。
- 定义为:给其他对象
function F(name) {
this.name = name
}
console.log(F.prototype.constructor === F)
console.log(Object.prototype.constructor === Object)
console.log(Number.prototype.constructor === Number)
/*
true
true
true
*/
- 原型链
- 基于上述的总结。一个对象的
prototype对象也应该有__proto__
.其值也对应于一个prototype对象。因此有原型链的产生。 - 因为JS中”万物都是对象“。所以该原型链的重点就是Object.prototype . 而 Object.prototype.proto 为 null
- 因此当我们去访问某个属性时,它会
按原型链去寻找
:- 自身的属性去找。
- 它所对应的原型对象上找
- 直到找到Object的原型对象。没有就为null。
- 基于上述的总结。一个对象的
function F(name) {
this.name = name
}
console.log(F.prototype)
console.log(F.__proto__ === Function.prototype)
console.log(Function.prototype.__proto__ === Object.prototype)
console.log(Object.prototype.__proto__)
- 原型继承:设置某个对象为另一个对象的原型。
- 显示原型继承: 我们需要手动实现过程。
- Object.setPrototype( target , source ) 。
将source作为target的原型对象
。 - Object.create( obj ) 。创建一个
新的对象,并把它作为创建的新对象的原型
。
- Object.setPrototype( target , source ) 。
- 通过obj.__proto__属性去访问 .
实际上是通过Object.defineProperty()
: 定义一个__proto__属性(加了get/set方法) . 这样就避免了不规范的操作.
- 显示原型继承: 我们需要手动实现过程。
let obj1 = {
a: 1
}
let obj2 = {
b: 2
}
// 设置obj2的原型对象为obj1
Object.setPrototypeOf(obj2, obj1)
console.dir(obj2)
console.log(obj1.prototype) //undefined
//create函数
Object.setPrototypeOf(obj2, obj1)
let obj3 = Object.create(obj2)
obj3.c = 3
console.dir(obj3)
let obj1 = {
a: 1
}
let obj2 = {
b: 2
}
Object.setPrototypeOf(obj2, obj1)
Object.defineProperty(obj2, '__proto__', {
get() {
console.log('get方法')
return Object.getPrototypeOf(this)
},
set(value) {
Object.setPrototypeOf(this, value)
}
})
console.log(obj2.__proto__)
-
隐式原型继承 : (
constructor函数
)- 创建空对象
- 设置空对象的原型为另一个对象 或者 null
- 为该对象 ,添加属性或方法.
- 若
没有隐式原型继承
. 我们需要手动去是实现这三步. - 因此使用constructor函数 (构造函数) . 来做属性初始化.
- constructor函数上 , 有prototype属性 .
- 通过new 关键词 , 去创建新对象 .
//F是构造函数. 默认做了F.prototype = Object.create(Object.prototype).
function F(name, age) {
this.name = name
this.age = age
}
let f = new F('fjh', 20)
console.log(f)
console.log(f.__proto__ === F.prototype)
console.log(F.prototype.__proto__ === Object.prototype)
- 上述基本了解了__proto__ 和prototype 这两个属性. 因此接下来 就是具体的继承实现了.
- 原型链的继承
- 问题:
- 原型中包含引用值的时候, 其
引用值会被所有的实例所共享
. 因此属性通常在构造函数上定义(实例属性)而不是在原型上定义(原型属性) . - 子类在实例化时 , 不能给父类型的构造函数传参.
- 原型中包含引用值的时候, 其
function F(name) {
this.name = name
this.arr = [1, 2, 3]
}
function S() {
}
S.prototype = new F('fjh')
let s = new S()
let s1 = new S()
s.arr[1] = 'change'
console.log(s1.arr)
// [1,' change ' , 3 ] . 发现修改了s , s1 也跟者变了.
// 并且初始化子类实例时 , 并没有参数. 参数是父类构造函数传入的.
- 盗用构造函数 (经典继承)
- 问题:
- 解决了原型链继承无法向父类传参的问题 . 但无法继承父类原型上的方法 .
- 且所有操作都必须通过子类 , 用call方法指定父类构造函数的this指向
function F(name) {
this.name = name
this.arr = [1, 2, 3]
}
function S(name) {
F.call(this, name)
}
let s = new S('fjh')
let s1 = new S('fjh1')
s.arr[1] = '改'
console.log(s, s1)
console.log(s.arr, s1.arr)
//因为函数的上下文对象是不断建立的.
//所以每新建一个子类的实例 . 它们继承的引用类型都是独立的 !
- 组合继承(融合了上述两种方法)
- 问题:
- 解决了属性的传递问题 . 以及方法的复用问题 .
- 但是它会改变子类实例的原型对象的constructor的指向.
- 问题:
function F(name) {
this.name = name
this.arr = [1, 2, 3]
}
F.prototype.doThing = () => {
console.log('something')
}
function S(name) {
F.call(this, name)
}
S.prototype = new F()
S.prototype.constructor === S
// Object.setPrototypeOf(S.prototype, F.prototype)
//这样写 . 可以达到上面两句的效果 .
let s = new S('fjh')
console.log(s)
console.log(S.prototype.constructor === S)
//根据上面所学, 函数对象的prototype都默认有个constructor属性 . 其指向它的构造函数 .
//也就是与它有关联的那个函数对象. 但此时constructor的指向其实是父类函数对象.
// 因此我们需要手动的去改变.
- 原型式继承
- 问题 :
- 适合不需要创建构造函数 . 但
仍需要在对象间共享信息
的场合 . - 但
属性中的引用值
始终会在相关对象间共享.
- 适合不需要创建构造函数 . 但
- 问题 :
function create(obj) {
function f() {}
f.prototype = obj
return new f()
}
let person = {
name: 'fjh',
arr: [1, 2, 3]
}
let p1 = create(person)
p1.name = 'p1'
p1.arr.push(4)
let p2 = create(person)
p2.name = 'p2'
console.log(p1, p2)
//这里可以不用新建create函数. 而是使用Object.create() . 也可以实现同样的效果.
- 寄生式继承
- 问题:
- 给对象添加函数导致函数难以重用 . 与构造函数模式类似
- 其背后的思路: 类似于寄生构造函数和工厂模式
- 问题:
function create(o) {
function F() {}
F.prototype = o
return new F()
}
function createAnother(original) {
let clone = create(original) //创建对象
clone.doThing = function() { //增强对象
console.log('clone')
}
return clone
}
let person = {
name: "fjh",
arr: [1, 2, 3]
}
let p1 = createAnother(person)
console.log(p1)
p1.doThing()
- 寄生式组合继承
- 可以算是引用类型继承的最佳模式
- inheritPrototype这个函数是核心 .
function create(o) {
function F() {}
F.prototype = o
return new F()
}
function inheritPrototype(subType, superType) {
let prototype = Object(superType.prototype) // 创建对象
prototype.constructor = subType // 增强对象
subType.prototype = prototype // 赋值对象
}
function SuperType(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
SuperType.prototype.sayName = function() {
console.log(this.name)
}
function SubType(name, age) {
SuperType.call(this, name)
this.age = age
}
inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function() {
console.log(this.age)
}
学习自深入理解JS原型
以及[ 红宝书 ]