博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Javascript中的原型继承的实现
阅读量:6233 次
发布时间:2019-06-21

本文共 3265 字,大约阅读时间需要 10 分钟。

这篇文章是翻译一篇文章《》

这是法国前端工程师Vjeux 2011年写的一篇文章,用假设、代码表现的方法,通俗易懂的解释了一下Javascript的设计原理,给人醍醐灌顶的感觉。希望你能从中能学到知识,如果内容有误,还请指出,多谢!

翻译:

      上网一搜,遍地都是写Javascript的原型继承这一概念的文章。但,Javascript实际上只是提供了一种方式,使得Javascript表现得像是其它语言中的原型继承——这个方式就是装饰符 new  。因此,网上大部分的解释很难理解,甚至更让你困惑。这篇文章就是解释了Javascript中的原型继承,和它到底是什么使用的。

原型继承定义

当你读到关于Javascript原型继承的文章时,时常看到它的解释:

当访问一个对象的属性时,Javascript会一直顺着原型链向上查找,直到找到这个属性名的值,或者在链的顶层都没找到,即为undefined。复制代码

一般情况下,我们会使用__proto__属性来表示原型链中上一层的对象。我们来看看__proto__和prototype的不同。

注:__proto__不是一个正式、标准的属性,不应该出现在你的代码里。    这篇文章里,它只是用来解释Javascript中原型继承怎么实现的。复制代码

下面代码说明了Javascript引擎如何查找一个属性的(只是为了通俗易懂的解释,不代表Javascript中是这么写的)。

function getProperty(obj, prop) {    if(obj.hasOwnProperty(prop)) {        return obj[prop];    else if(obj.__proto__ !== null)        return getProperty(obj.__proto__, prop);    else         return undefined}复制代码

我们来看个例子:一个抽象类 —— 坐标点 Point,它有两个坐标值x,y和一个方法print

按照上面的方法 getProperty,我们来实现下原型继承。我们可以定义一个对象Point,有三个属性:x,y,print。要想生成一个新的point,我们只需要将新的Point的__proto__设置成为Point就可以了。(创建了一个原型链,Point为原型链上的上层)

var Point = {    x:0,    y:0,    print: function() {console.log(this.x,this.y);}};var p = {    x: 10,    y: 20,    __proto__: Point}p.print();  //10,20复制代码

Javascript中诡异的继承

奇怪的是,在上面那个定义下写出来的代码是不成立的。实际使用的方式时下面这种:

function Point(x,y) {    this.x = x;    this.y = y;}Point.prototype = {    print: function() {        console.log(this.x,this.y);    }};var p = new Point(10,20);p.print();复制代码

这个上面的代码完全不同。Point现在是一个函数,我们用了prototype属性,还用了new装饰符。怎么回事?

new是怎么工作的?

Brendan Eich设计Javascript时,参考了当时流行的面向对象语言C++、Java,借鉴了继承的定义方式 new: :new来生成一个类的实例。

  • C++有构造函数的概念,用来初始化实例的属性。因此,new装饰符必须指向一个函数。
  • 我们需要找个地方放置公共方法、公共属性。由于我们正在使用的是一个继承性的语言,那么就把它放在函数的一个属性里,名字是prototype。

new装饰符后面跟着一个函数F,参数arguments:new F(arguments...)。它只做了简单的三步:

  1. 生成类的实例。就是一个只有一个属性__proto__的对象。__proto__的值为F.prototype
  2. 初始化实例。函数F被调用,且 this 指向该实例。
  3. 返回这个实例。

现在,我们知道了new做了什么,最后生成了什么,我们用Javascript来演示一下new方法。

function New(f) {    var n = {__proto__: f.prototype};  //1    return function() {        f.apply(n, arguments);         //2        return n;                      //3    }}//这里可能不好理解,实际上作者是这么设置的,new和函数名称先执行,返回一个函数后,执行这个函数,即:var f = (new F)(10,10);先执行前一个函数,然后再执行后面的入参。复制代码

再举一个小例子,帮助大家理解

function Point(x,y) {    this.x = x;    this.y = y;}Point.prototype = {    print: function() {console.log(this.x,this.y);}}var p1 = new Point(10,10);p1.print(); //10,10console.log(p1 instanceof Point);var p2 = new Point(20,20);p2.print(); //20,20console.log(p2 instanceof Point);复制代码

Javascript中真正的原型继承

Javscript规范中,只提供了new装饰符的使用方式。然后,Douglas Crockford使用了一种方式,让new去做真正的原型继承的工作。那就是重写Object.create()。

Object.create = function(parent) {    function F() {}    F.prototype = parent;    return new F();}复制代码

看起来比较奇怪,实际上它做的工作很简单。就只是创建了一个新对象,它的prototype可以设置为任意值。如果可以使用__proto__的话,它可以更加简略:

Object.create = function(parent){    return { __proto__: parent };}复制代码

还是上面Point的例子,我们用Object.create来实现:

var Point = {    x:0,    y:0,    print: function() {console.log(this.x,this.y);}};var p = Object.create(Point);p.x = 10;p.y = 20;p.print(); //10 20复制代码

总结

上面已经讲清楚了什么是原型继承,Javascript是怎样通过一种特定的方式完成原型继承的。

然而,真正实现继承的方式(Object.create和__proto__)有些缺点:

  • 规范不允许:__proto__不是标准的属性,甚至是被反对使用的。原生的Object.create()和douglas Crockford的使用也是不完全相同的。
  • 不是最优化方案:Object.create(原生抑或自定义的),远没有使用new的性能好。甚至能慢到10倍以上。

转载于:https://juejin.im/post/5ca312266fb9a05e3709330a

你可能感兴趣的文章
C++ C# python 中输入输出函数对比
查看>>
Java 入门
查看>>
test4 结对项目
查看>>
idea老版本下载
查看>>
SQL SERVER 2008 多边形问题的解决
查看>>
RTEMS进程同步机制
查看>>
关于访问MSMQ远端私有队列的一点经验
查看>>
前端表单校验插件 jquery.validate.min.js自定义校验规则
查看>>
MySQL系列:高可用架构之MHA
查看>>
python堡垒机开发
查看>>
共享内存
查看>>
关于this
查看>>
用户登录(二次机会)且每次输错误时显示剩余错误次数(提示:使用字符串格式化)...
查看>>
[转载][转帖]谈谈我对攻读计算机研究生的看法。。。大牛的文章,见解精深独到...
查看>>
使用Python进行AES加密和解密
查看>>
Unity_UIWidgets学习笔记03_组件_Image
查看>>
linux cat 命令详解
查看>>
转.给android设备安装busybox
查看>>
Docker swarm集群增加节点和删除节点
查看>>
将 年-月-日 封装成tree树状结构
查看>>