责任编辑译者为奇舞蹈团前端开发技师
简述
JavaScript 是 Web 的编程词汇,单纯初学,使用方便,但由于过分灵巧结构设计经营理念,导致新手时常满脸懵,责任编辑要谈的是JavaScript中症结之一蓝本链。
蓝本链的今生
JavaScript的问世
要认知Javascript的蓝本链的结构设计思想,要从它的问世讲起。
1994年,Novell(Netscape)发布了Navigator应用程序0.9版。这是历史上第二个比较成形的互联网应用程序,彼时名噪一时。但这个版的应用程序根本无法用以下载,不具备与访客交互的能力。比如说,假如页面上有附注”使用者名”明确要求核对,应用程序就无法推论访客与否真的核对了,根本无法让应用程序将页面全部发回服务端,让服务器端推论与否核对。假如没有核对,服务器端就回到严重错误,明确要求使用者重新核对,这太无用和互联网资源了。
因此,Novell亟需一类页面脚本词汇,使得应用程序能与页面交互。技师Brendan Eich负责开发此种新词汇。他觉得,没必要性结构设计得很繁杂,此种词汇只要能够完成一些单纯操作方式就够了,比如说推论使用者是不是核对配置文件。
1994年正是面向第一类(object-oriented programming)最繁盛的时期,C++是彼时最盛行的词汇,而Java词汇的1.0版将要于第三年推出,Sun公司正在肆意摇旗呐喊。
Brendan Eich毫无疑问受到了影响,Javascript里头所有的正则表达式都是第一类(object) ,这一点与Java非常相近。但,他旋即就碰到了一个痛点,究竟要不要结构设计”承继”监督机制呢?
JavaScript是两门脚本词汇,是为了操作方式页面的,假如只将其作为固定式的脚本词汇,其实不需要有”继承”监督机制。但Javascript里头都是第一类,要有一类监督机制,能将第一类之间关连起来。所以Brendan Eich最后还是结构设计了”承继”。
但他不急于导入”类”(class)的概念,因为一旦有了”类”,Javascript就是一类完备的面向第一类词汇了,这好像有点太正式了,Brendan Eich考虑到C++和Java词汇都使用new指示聚合示例。
C++的读法是:
Java的读法是:
因此,他把new指示导入了Javascript,用以从类(JavaScript中叫蓝本第一类)聚合一个示例第一类。但Javascript没有”类”,怎么来表示类(蓝本第一类)呢?
这时,他想到C++和Java使用new指示时,都会调用”类”的构造函数(constructor)。他就做了一个简化的结构设计,在Javascript词汇中,new指示后面跟的不是类,而是构造函数。
举例来说,现在有一个叫做Person的构造函数,表示人第一类的蓝本(能认知成java中的类)。
对这个构造函数使用new,就会聚合一个人第一类的示例。
注意构造函数中的this关键字,它就代表了新创建的示例第一类。
prototype属性的由来
对于面向第一类词汇比如说java或者c++来说,用构造函数聚合示例第一类是无法共享属性和方法,都有其独立的内存区域,互不影响。
比如说,在Person第一类的构造函数中,设置一个示例第一类的共有属性race。
然后,聚合两个示例第一类:
这两个第一类的race属性是独立的,修改其中一个,不会影响到另一个。
每一个示例第一类,都有自己的属性和方法的副本。
按前面new运算符所述,每一个示例第一类,都有自己的属性和方法的副本。但此时假如我们想在同类但不同第一类间共享数据(承继)怎么办呢,解决此问题的方法就是prototype。
考虑到共享数据的问题,Brendan Eich决定为JavaScript的构造函数设置一个prototype属性。
这个属性包含一个第一类(以下简称”prototype第一类”),所有示例第一类需要共享的属性和方法,都放在这个第一类里头;那些不需要共享的属性和方法,就放在构造函数里头。这里prototype第一类有点像C++基类。
示例第一类一旦创建,将自动引用prototype第一类的属性和方法。也就是说,示例第一类的属性和方法,分成两种,一类是本地的,另一类是引用的。
还是以Person构造函数为例,现在用prototype属性进行改写:
现在,race属性放在prototype第一类里,是两个示例第一类共享的。只要修改了prototype第一类,就会同时影响到两个示例第一类。
综上所述,由于所有的示例第一类共享同一个prototype第一类,那么从外界看起来,而示例第一类则好像”承继”了prototype第一类一样。
这就是Javascript承继监督机制的结构设计思想。
重写prototype属性、方法
用过java、c++类词汇的都知道,既然有承继,必然有重写。举例说明JavaScript的重写
控制台输出:
img此时我们打印pA、pB,我们惊喜的发现,他们有了属性hairColor和eat方法;示例动态的获得了Person构造函数之后添加的属性、方法,这就是蓝本意义所在!能动态获取,这样能节省内存。
另外我们还要注意:假如pA将头发染成了黄色,那么hairColor会是什么呢?
控制台输出:
img能看到,pA的hairColor = yellow, 而pB的hairColor = black;示例第一类重写蓝本上承继的属性、方法,相当于 “属性覆盖、属性屏蔽” ,这一操作方式不会改变蓝本上的属性、方法,自然也不会改变由统一构造函数创建的其他示例,只有修改蓝本第一类上的属性、方法,才能改变其他示例通过蓝本链获得的属性、方法。
承继与蓝本链
JavaScript 中一切皆第一类。每个示例第一类都有一个私有属性,称之为 proto,指向它的构造函数的蓝本第一类(prototype)。该蓝本第一类也有一个自己的蓝本第一类(proto),层层向上直到一个第一类的蓝本第一类为 null。根据定义,null 没有蓝本,并作为这个蓝本链中的最后一个环节。
蓝本链的经典图:img这张图详细的描述了构造函数Function,Object以及它们示例之间的蓝本关系。
首先得记住并认知几个概念
属性proto是一个第一类,它有两个属性,constructor和proto;
蓝本第一类prototype有一个默认的constructor属性,用于记录示例是由哪个构造函数创建;
除了Object的蓝本第一类(Object.prototype)的proto指向null,其他内置函数的蓝本第一类和自定义构造函数的蓝本第一类的 proto都指向Object.prototype
结合一张pB第一类的结构图看就更容易认知了:
img创建第一类的方法
使用语法结构创建
使用构造器创建
在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作方式符 来作用这个函数时,它就能被称为构造方法(构造函数)。
使用 Object.create 创建
ECMAScript 5 中导入了一个新方法:Object.create()。能调用这个方法来创建一个新第一类。新第一类的原型就是调用 create 方法时传入的第二个参数:
class关键字
ECMAScript6 导入了一套新的关键字用以实现 class。使用java、swift等面向第一类的词汇的开发人员会对这些结构感到熟悉,但它们是不同的。JavaScript 仍然基于蓝本。这些新的关键字包括 class, constructor,static,extends 和 super。
注意:类的本质还是一个函数,类就是构造函数的另一类读法,JavaScript 中并没有一个真正的 class 原始类型, class 、extends 仅仅只是对蓝本第一类运用语法糖,这样便于程序员认知。
参考
轻松认知JS 蓝本蓝本链 https://juejin.cn/post/6844903989088092174
Javascript承继监督机制的结构设计思想 https://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
承继与蓝本链 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain




