让我们回顾一下,this 指向里提到的new关键字执行过程。
手写new关键字的执行过程:
function myNew(fn, ...args) { // 构造函数作为参数
let obj = {}
obj.__proto__ = fn.prototype
fn.apply(obj, args)
return obj
}
这里提到了__proto__ 和prototype:前者被称为隐式原型,后者被称为显式原型。
三者的概念
构造函数:用于生成实例对象。构造函数可分为两类:
function foo () {}function Function () {}和function Object () {}等原型对象:每个构造函数都有自己的原型对象,可通过prototype访问。
实例对象:可由构造函数通过new关键字生成的对象。
三者的关系

构造函数可以通过prototype访问其原型对象,而原型对象可通过constructor访问其构造函数。构造函数可通过new关键字创建实例对象,实例对象可通过__proto__ 访问其原型对象。
我们来看一段代码的输出结果:
function Foo(name, age) {
this.name = name
this.age = age
}
let a = new Foo('小明', 22)
console.log('构造函数:', Foo)
console.log('原型对象', Foo.prototype)
console.log('实例对象', a)
// 可以输出一下,看看它们都是什么样子

可以看出实例对象内部的第一个[[Prototype]]的展开内容等于原型对象的展开内容,可构建一个等式如下:
// 实例对象可通过 __proto__ 访问其原型对象
a.__proto__ === Foo.prototype // true
// 原型对象可通过 constructor 访问其构造函数
Foo.prototype.constructor === Foo // true
来看一张关于原型链的经典图:

上面这张图的箭头乍一看能让人头疼,我们对图中的元素进行分类并划分层次,可有以下三层:
__proto__指向:实例对象// 生成实例对象
function Foo() {}
let obj1 = new Foo()
// __proto__指向验证
obj1.__proto__ === Foo.prototype // true
new Object()、对象字面量生成的实例对象// 生成实例对象
let obj2 = new Object()
// __proto__指向验证
obj2.__proto__ === Object.prototype // true
function或class声明生成的实例对象// 生成实例对象
function Foo(){}
// 原生构造函数
// function Function(){}
// function Object(){}
// __proto__指向验证
Foo.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototype // true
Object.__proto__ === Function.prototype // true
说明:其实我们自己定义的函数也是由Function构造函数生成的实例对象。
__proto__指向:Function.prototype和Foo.prototypeFoo.prototype.__proto__ === Object.prototype // true
Function.prototype.__proto__ === Object.prototype // true
__proto__指向:Object.prototype)Object.prototype.__proto__ === null // true
我们自己再画一张图看一下:

自底向上有三层的__proto__构成基本的JavaScript原型模式生态,最后再总结一下规则:
Object.prototypeObject.prototype最终指向null总结:其实我们的原型链指的就是__proto__的路径。
注意:这里只是为了原型链能更加直观,请不要忘了构造函数原型的constructor属性,它会指回对应的构造函数。
我们利用任务驱动型的方法去学习继承方式,考虑这样一个类结构:
说明:VIP用户需要继承普通用户。其中,VIP用户的武器列表可以添加屠龙宝刀。
原型链搜索机制:若要访问当前对象所没有的属性和方法,则会首先以当前对象为起点沿着原型链__proto__向上寻找每个对象内部的属性和方法。直到找到对应的属性和方法,没有则会直接走到原型链尽头null。
来看这样一段代码:
function USER(username, password) {
this.username = username
this.password = password
this.weapon = ['水果小刀']
}
VIP.prototype = new USER() // 为什么要放到中间?
// 注意:改写原型,要记得把 constructor 指会原构造函数
VIP.prototype.constructor = VIP
function VIP() { }
VIP.prototype.addWeapon = function (weaponName) {
this.weapon.push(weaponName)
}
let a = new VIP('小明')
// 缺陷1:无法给父类构造函数传参,只能在 VIP 中自行添加相应参数。无法实现父类属性重用
let b = new VIP()
b.addWeapon('屠龙宝刀')
console.log(b.weapon)
let c = new VIP()
console.log(c.weapon)
// 缺陷2: 我们想要单独给实例 b 的武器列表添加一把屠龙宝刀,结果是实例 c 的武器列表也会增加屠龙宝刀
原型链__proto__实现继承会经过的对象(从子类实例到父类原型):
子类构造函数实例 :new VIP()
父类构造函数实例 :new USER()
父类构造函数原型 :USER.prototype
我们可以构建两个表达式去验证:
new VIP().__proto__ === new USER() // true
new USER().__proto__ === USER.prototype // true
再进一步提炼以上两个表达式,可获得最终表达式。以下为实现继承关键的完整原型链:
// VIP 构造函数所生成的实例会经过两层__proto__找到父类原型
new VIP().__proto__.__proto__ === USER.prototype
// 接下来,由 VIP 构造函数生成的实例所没有的属性和方法,都会去父类原型找到属性和方法。
原型链继承缺点:
为什么 VIP.prototype = new USER() 这一步要放到两个构造函数中间?
如果这一步表达式放到后面,我们的VIP.prototype是其原本构造函数 VIP 的原型。在这个原本的构造函数原型上添加方法,不会有继承效果。
我们的想法是通过父类构造函数生成实例,利用它实例的__proto__去实现继承效果。要想在子类构造函数添加方法,我们实际做了这样的操作。如下:
// new USER()就是我们父类构造函数生成的实例
new USER().addWeapon = function (weaponName) {
this.weapon.push(weaponName)
}
但是上面这样会出现问题,我们子类构造函数怎么办?他想new一个实例,还是会根据原来的原型。
因此,我们需要将new USER()传递给VIP.prototype。这样VIP构造函数生成实例才会有继承效果,如下:
VIP.prototype = new USER() // 传递__proto__实现继承
VIP.prototype.addWeapon = function (weaponName) {
this.weapon.push(weaponName)
}
来看这样一段代码:
function USER(username, password) {
this.username = username
this.password = password
this.weapon = ['水果小刀']
}
function VIP(username, password) {
USER.call(this, username, password) // 调用父类构造函数,为其属性赋值
} // 这里的 this 指向子类构造函数生成的新实例
// 1. 接下来我们可以向父类构造函数传参
let a = new VIP('小红')
console.log(a.username) // 小红
// 2. 也可以解决引用值产生的问题
let b = new VIP()
b.weapon.push('屠龙宝刀')
console.log(b.weapon) // ['水果小刀', '屠龙宝刀']
let c = new VIP()
console.log(c.weapon) // ['水果小刀']
// 这样实例 b 和 c 的武器列表的数据都是独立的
过程解析:new VIP('小红')传入了一个“小红”参数。
第一次绑定操作:new执行过程会执行一次绑定操作,将this指向实例对象。
第二次绑定操作:VIP构造函数内部的call方法再次绑定实例对象,调用父类构造函数
总结:我们通过传参实际调用了两次绑定操作,最终使得子类构造函数的新实例也能拥有父类的属性和值。
盗用构造函数缺点:
如果你已经清楚的知道上面两种继承方式的优点和缺陷,那么我们可以利用1 + 1 > 2 的方法实现组合继承。
function USER(username, password) {
this.username = username
this.password = password
this.weapon = ['水果小刀']
}
VIP.prototype = new USER()
// 注意:改写原型,要记得把 constructor 指会原构造函数
VIP.prototype.constructor = VIP
function VIP(username, password) {
USER.call(this, username, password) // 调用父类构造函数,为其属性赋值
} // 这里的 this 指向子类构造函数生成的新实例
VIP.prototype.addWeapon = function (weaponName) {
this.weapon.push(weaponName)
}
// 我们尝试给父类构造函数传参
let a = new VIP('小红')
console.log(a.username) // 小红
// 看看添加屠龙宝刀,有没有相互影响
let b = new VIP()
b.addWeapon('屠龙宝刀')
console.log(b.weapon)
let c = new VIP()
console.log(c.weapon)
以上的组合继承方式输出了正确的答案,算是完美解决了原型链继承和盗用构造函数继承出现的问题。我们将以上代码放入浏览器打断点分析。如下:
很明显,我们第一次new USER()会调用父类构造函数,而后子类构造函数每一次生成新实例都会调用父类构造函数。也就是说,多了第一次会调用父类构造函数的情况。
在 JavaScirpt 高级程序设计 8.3.4 中提到了这种方式,来看这样一段代码
function object(obj) {
function Fn() { }
Fn.prototype = obj
return new Fn() // 返回一个空函数,其内部原型改写为 obj
}
有没有熟悉的感觉,其实正是我们之前手写bind函数利用的继承方法。与ES6中的Object.create()方法效果相同。它适于在原有对象的基础上再克隆一个对象。此外,对象属性值若为原始值则可以进行改写,若为引用值则会产生引用值的特点。即多个克隆对象会共享同一个引用值,也就是说这个“克隆”操作相当于我们的浅拷贝操作。
function createAnother(obj) {
let clone = object(obj)
clone.sayHello = () => {
console.log('Hello World')
}
}
这种方式可以使克隆对象在原基础上增强,即添加属性和方法,
注意:原型继承和寄生继承都重点关注对象的使用,而不考虑构造函数的使用
我们可以再利用浏览器打断点试试,是不是不会发生像组合继承那样首次调用构造函数的情况。
// 寄生组合继承
function inheritPrototype(subType, superType) {
subType.prototype = Object.create(superType.prototype) // 创建对象
subType.prototype.constructor = subType // 增强对象
}
function USER(username, password) {
this.username = username
this.password = password
this.weapon = ['水果小刀']
}
// 验证表达式时,下面这一条语句要加上注释。
inheritPrototype(VIP, USER) // 调用继承函数,
// 验证表达式时,把下面这两条语句注释去掉。
// VIP.prototype = Object.create(USER.prototype) // 创建对象
// VIP.prototype.constructor = VIP // 增强对象
function VIP(username, password) {
USER.call(this, username, password) // 调用父类构造函数,为其属性赋值
} // 这里的 this 指向子类构造函数生成的新实例
VIP.prototype.addWeapon = function (weaponName) {
this.weapon.push(weaponName)
}
// 我们尝试给父类构造函数传参
let a = new VIP('小红')
console.log(a.username) // 小红
// 看看添加屠龙宝刀,有没有相互影响
let b = new VIP()
b.addWeapon('屠龙宝刀')
console.log(b.weapon)
let c = new VIP()
console.log(c.weapon)
原型链__proto__实现继承会经过的对象(从子类实例到父类原型):
new VIP()Object.create(USER.prototype)USER.prototype我们同样构建两个表达式去验证:
new VIP().__proto__ === Object.create(USER.prototype) // true
Object.create(USER.prototype).__proto__ === USER.prototype // true
再进一步提炼以上两个表达式,可获得最终表达式。以下为实现继承关键的完整原型链:
new VIP().__proto__.__proto__ === USER.prototype // true
// 接下来,由 VIP 构造函数生成的实例所没有的属性和方法,都会去父类原型找到属性和方法。
原型链和寄生组合的继承区别比较
原型链的继承实现:利用new USER()作为跳板实现继承。
VIP.prototype = new USER() // 传递__proto__实现继承
new VIP().__proto__ === new USER() // true
new USER().__proto__ === USER.prototype // true
寄生组合的继承实现:利用Object.create(USER.prototype)作为跳板实现继承。
VIP.prototype = Object.create(USER.prototype) // 传递__proto__实现继承
new VIP().__proto__ === Object.create(USER.prototype) // true
Object.create(USER.prototype).__proto__ === USER.prototype // true
注意:Object.create(USER.prototype)会返回一个空函数实例,这个实例的__proro__指向USER()构造函数。
在 ES5 之前我们都是利用构造函数实现面向对象编程。ES6 的class作为语法糖,其实内部也是利用了构造函数实现面向对象编程。
class USER {
constructor(username, password) {
this.username = username
this.password = password
this.weapon = ['水果小刀']
}
}
class VIP extends USER {
constructor(username, password) {
super(username, password) // 调用父类构造函数,相当于执行 call 方法
}
addWeapon(weaponName) {
this.weapon.push(weaponName)
}
}
// 我们尝试给父类构造函数传参
let a = new VIP('小红')
console.log(a.username) // 小红
// 看看添加屠龙宝刀,有没有相互影响
let b = new VIP()
b.addWeapon('屠龙宝刀')
console.log(b.weapon)
let c = new VIP()
console.log(c.weapon)
// 以上同样可运行我们的测试代码
总结 JavaScript 继承方式:
以上可以看出 JavaScript 对与继承方式的优化是一个多次迭代不断优化的过程。
参考
在我的系统中,我已经定义了STI。Dog继承自Animal,在animals表中有一个type列,其值为"Dog"。现在我想让SpecialDog继承自dog,只是为了在某些特殊情况下稍微修改一下行为。数据还是一样。我需要通过SpecialDog运行的所有查询,以返回数据库中类型为Dog的值。我的问题是因为我有一个type列,rails将WHERE"animals"."type"IN('SpecialDog')附加到我的查询中,所以我不能获取原始的Dog条目。所以我想要的是以某种方式覆盖rails在通过SpecialDog访问数据库时使用的值,使其表现得像Dog。有没有办法覆盖用于类型
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图
关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。关闭3年前。Improvethisquestion我正处于学习Ruby的阶段,我想查看一些小型库的源代码以了解它们是如何构建的。我不知道什么是小型图书馆,但希望SO能推荐一些易于理解的图书馆来学习。因此,如果有人知道一两个非常小的库,这是新手Rubyists学习的好例子,请推荐!我想使用Manveru'sInnatelib,因为它试图保持在2000LOC以下,但我还不熟悉其中经常使用的Ruby速记。也许大约100-5
由于匿名block和散列block看起来大致相同。我正在玩它。我做了一些严肃的观察,如下所示:{}.class#=>Hash好的,这很酷。空block被视为Hash。print{}.class#=>NilClassputs{}.class#=>NilClass为什么上面的代码和NilClass一样,下面的代码又显示了Hash?puts({}.class)#Hash#=>nilprint({}.class)#Hash=>nil谁能帮我理解上面发生了什么?我完全不同意@Lindydancer的观点你如何解释下面几行:print{}.class#NilClassprint[].class#A
所以我只是对此感到好奇:DataMapper为其模型使用混合classPostincludeDataMapper::Resource虽然active-record使用继承classPost有谁知道为什么DataMapper选择这样做(或者为什么AR选择不这样做)? 最佳答案 它允许您从另一个不是DM类的类继承。它还允许动态地将DM功能添加到类中。这是我正在处理的模块中的类方法:defdatamapper_classklass=self.dupklass.send(:include,DataMapper::Resource)klass
我很难理解Ruby中sender和receiver的实际含义。它们一般是什么意思?到目前为止,我只是将它们理解为方法调用和获取其返回值的调用。但是,我知道我的理解还远远不够。谁能给我一个Ruby中发送者和接收者的具体解释? 最佳答案 面向对象中的一个核心概念是消息传递和早期概念化,这在很大程度上借鉴了计算的Actor模型。艾伦·凯(AlanKay)创造了面向对象一词并发明了最早的OO语言之一SmallTalk,他拥有voicedregretatusingatermwhichputthefocusonobjectsinsteadofo
我开始了一个新的Rails3.2.5项目,Assets管道不再工作了。CSS和Javascript文件不再编译。这是尝试生成Assets时日志的输出:StartedGET"/assets/application.css?body=1"for127.0.0.1at2012-06-1623:59:11-0700Servedasset/application.css-200OK(0ms)[2012-06-1623:59:11]ERRORNoMethodError:undefinedmethod`each'fornil:NilClass/Users/greg/.rbenv/versions/1
rails新手。只是想了解\assests目录中的这两个文件。例如,application.js文件有如下行://=requirejquery//=requirejquery_ujs//=require_tree.我理解require_tree。只是将所有JS文件添加到当前目录中。根据上下文,我可以看出requirejquery添加了jQuery库。但是它从哪里得到这些jQuery库呢?我没有在我的Assets文件夹中看到任何jquery.js文件——或者直接在我的整个应用程序中没有看到任何jquery.js文件?同样,我正在按照一些说明安装TwitterBootstrap(http:
我正在使用带有单个“帐户”表的STI模型来保存用户和技术人员的信息(即用户...8)错误:test_the_truth(用户测试):ActiveRecord::StatementInvalid:PGError:ERROR:关系“技术人员”不存在:从“技术人员”中删除...从本质上讲,标准框架不承认Technicians和Users表(或PostgreSQL称它们为“关系”)不存在,事实上,应该别名为Accounts。有什么想法吗?我对RoR比较陌生,不知道如何解决这个问题而又不完全删除STI。 最佳答案 原来问题是由于存在:./te