对原型链和js中的继承的一些理解

1.属性设置和屏蔽

我们首先来完整讲解一下给对象设置属性的过程

ini 复制代码
myObject.foo = 'bar'

如果myObject对象中包含foo的普通数据访问属性(对象的一些理解 - 掘金 (juejin.cn)),这条赋值语句只会修改已有的属性值。

如果foo即出现在myObject中也出现在myObject的原型链上层,那么就会发生屏蔽。myObject.foo会选择原型链中最底层的foo属性。

如果foo只出现在myObject.foo原型链上层会出现三种情况:

  1. 如果原型链上层存在名为foo的普通数据访问属性,并且没有被标记为只读(writable:false),那么就会在myObject中添加一个名为foo的新属性,这是屏蔽属性
  2. 如果原型链上层存在名为foo的普通数据访问属性,并且被标记为只读(writable:false)。在严格模式下会报错,非严格模式下会发生屏蔽。(只读属性会阻止原型链下层隐式创建同名属性,是为了模拟类属性的继承,可以看成这个属性会被myObject继承,这样一来myObject的foo属性也是只读了)
  3. 如果原型链上层存在名为foo并且是一个setter,那么就一定会调用这个setter。foo不会被添加到myObject中,也不会重新定义foo这个setter

如你所见,三种情况只有一种是发生了属性屏蔽的。如果希望在第2、3种情况下也屏蔽foo,那就不能使用=操作符来赋值,而是使用Object.defineProperty()来向myObject添加foo

javascript 复制代码
const anotherObject = {
    a:2
}

var myObject = Object.create(anotherObject)
myObject.a++
anotherObject.a //2
myObject.a//3

这里的myObject.a++通过委托找到anotherObject.a的值为2,但是myObject.a++相当于myObject.a = myObject.a + 1,这里通过=运算符将3这个值赋给了myObject,发生了属性屏蔽。如果想要anotherObject.a的值增加只能通过anotherObject.a++

2.'类'函数

实际上,JavaScript中的'类'和面向类的语言中是不同的,在面向类的语言中,类可以被复制(或者说实例化)很多次,就像用模具制作东西一样。

在JavaScript中,我们通过new Foo()生成一个新对象时,这个新对象的原型链上就会被关联到Foo.prototype对象。这两个对象是互相关联的,我们没有初始化一个类,也没有从'类'中复制任何行为到一个对象中,我们只是让这两个对象互相关联。

所以我们常说的'原型继承'其实不太准确,继承意味着复制操作,JavaScript(默认)并不会复制对象属性。相反,JavaScript会在两个对象之间创建一个关联,这样一个对象可以通过委托访问另一个对象的属性和函数。委托这个术语更准确地描述了对象中的关联机制

3.(原型)继承

我们已经看过许多JavaScript程序中常用的模拟类行为的方法,但是如果没有'继承',JavaScript的类只是一个空架子

javascript 复制代码
function Foo(name){
    this.name = name
}
Foo.prototype.myName = function(){
    return this.name
}
function Bar(name,label){
    Foo.call(this,name)
    this.label = label
}
Bar.prototype = Object.create(Foo.prototype)
Bar.prototype.myLabel = function(){
    return this.label
}

var a = new Bar('a','obj a')
a.myName() //'a'
a.myLabel() //'obj a'

这里我们为了使用到Foo.prototype的myName方法,使用了Bar.prototype = Object.create(Foo.prototype)。这段代码的意思是:Object.create()会凭空创造一个'新'对象并将新对象内部的__proto__值关联到你指定的对象(本例中是Foo.prototype)。换句话是,创建一个新的Bar.prototype对象并把它关联到Foo.prototype对象,即此时Foo.prototype.__ proto__ === Foo.prototype

注意,以下两种方式是常见的错误做法,实际上他们都存在一些问题

javascript 复制代码
//和你想要的机制不一样
Bar.prototype = Foo.prototype

//基本上满足你的需求,但是可能会产生一些副作用
Bar.prototype = new Foo()

Bar.prototype = Foo.prototype并不会让两个对象产生关联,它只是让Bar.prototype 直接引用 Foo.prototype对象,但我们执行类似Bar.prototype.myLabel时会直接修改Foo.prototype对象本身。显然这不是你想要的结果,否则根本不需要Bar对象,直接使用Foo就好。

Bar.prototype = new Foo()的确会让Bar.prototype 关联到 Foo.prototype,但是这里使用了Foo()函数的'构造函数调用',如果Foo有一些副作用(比如输出日志,给this添加数据属性),就会影响到Bar()的后代,后果不堪设想。

因此要创建一个合适的关联对象,我们要使用Object.create(),这样做的唯一缺点是需要创建一个新对象然后旧对象会被垃圾回收,不能直接修改已有的默认对象

4.行为委托

在软件架构中,大多数开发者理所应当的地认为类是唯一的代码组织方式,但是现在我们知道了更强大的设计模式:行为委托。我们并不像类一样复制父类的属性和方法,只是遇到没有找到的属性或者方法,我们会委托给原型链上的对象。

行为委托更像是兄弟关系,互相委托,而不是父类和子类的关系,原型链的机制本质上就是行为委托机制。我们可以在JavaScript中努力实现类机制(继承、混入、多态),也可以拥抱原型链的委托机制

相关推荐
打不着的大喇叭41 分钟前
uniapp的光标跟随和打字机效果
前端·javascript·uni-app
无我Code1 小时前
2025----前端个人年中总结
前端·年终总结·创业
程序猿阿伟1 小时前
《前端路由重构:解锁多语言交互的底层逻辑》
前端·重构
Sun_light1 小时前
6个你必须掌握的「React Hooks」实用技巧✨
前端·javascript·react.js
爱学习的茄子1 小时前
深度解析JavaScript中的call方法实现:从原理到手写实现的完整指南
前端·javascript·面试
莫空00001 小时前
Vue组件通信方式详解
前端·面试
呆呆的心1 小时前
揭秘 CSS 伪元素:不用加标签也能玩转出花的界面技巧 ✨
前端·css·html
百锦再1 小时前
重新学习Vue中的按键监听和鼠标监听
javascript·vue.js·vue·计算机外设·click·up·down
susnm1 小时前
Dioxus 与数据库协作
前端·rust
优雅永不过时_v1 小时前
基于vite适用于 vue和 react 的Three.js低代码与Ai结合编辑器
前端·javascript