剪不断,理还乱,是离愁。

前面已经提到过新语言开发的两个步骤,分别是:一、定义基本的数据类型,完善结构化编程语言的设计;二、为函数类型绑定this的概念,好在对象的方法中可以引用到对象自身。下面是继续下去的思路,其主体思想是尽可能地引用传统面向对象语言的相关概念(如类、继承等)到新语言中来。

三、让对象属于某个类

这次要引入类的概念来。但是注意的是,还是前面提到过的思路,是让对象看起来属于某个类,而不是真正地构造基于类的种种语义概念。

一般来说,类包括类符号和类模板。最简单的类符号可以是一个字符串属性。比如随便一个对象,它的class属性来指明类名,即调用obj.class返回类名字符串(如"Cat", "Dog"等)。不过麻烦的是类模板。我们要在统一的地方定义类模板,以便于在用统一的模式创建类实例。这样创建的类实例(即对象)才是真正有意义的。因为我们不仅要在类符号上来区别类;更重要的是,要在类行为上来统一类。

这时想到的是函数。因为在JavaScript中,一切对象的创建通过一段代码块来实现的,而函数又能够将这段代码块组合起来。所以,可以让一般的函数作为类模板的定义;进一步地,将它视为构造方法。

一般的函数定义是:

function createStudent(name, age) {
var student = {};
student.name = name;
student.age = age;
student.toString = function() {
return this.name + " " + this.age;
};
student.class = 'Student';
return student;
}

这样才能够完整地表现我们之前的一些概念。而且,这种方式没有引入任何新的语言概念。只不过,这种构造方式要完全依赖开发人员去实现。语言本身并不能自动支持其中的任何一个概念。

因此,当时的JavaScript设计者进一步地推动了在语言自身中去自动实现一些概念。分为以下几步:

1. 引入构造函数的概念

上面的createStudent多多少少不是构造函数的样子。像Java和C++那样的面向对象语言,当调用构造函数时,对象已经创建好了。构造函数完成的是一些初始化的工作。根本就不需要像第2行代码那样去显示地创建对象。所以,前面的代码要改写成下面的样子:

function Student(name, age) {
this.name = name;
this.age = age;
this.toString = function() {
return this.name + " " + this.age;
};
}

这里主要做了以下几点改动:

1. 函数名不再是createStudent,而是Student,这看起来更像是构造函数的名字。
2. 不再显示地创建对象;而是当函数作为构造函数调用时,会默认构造一个空对象,并能够通过this访问。
3. 函数不再return返回任何对象;而是默认地,应该返回this指向地对象。
4. 不再有任何显示地构造this.class = "Student"之类的语句;而是默认地,这一构建应该在构造过程中自动完成。

只不过,这里说是这样说,要达到这一系列的改动必须要做一些工作。如果还像以前那样调用Student函数时达不到上面提到的四点效果的(说错了,其实第一点效果达到了)。有兴趣的同学可以自己揣摩下。

为达到上面提到的四点效果,JavaScript设计者引入一个新的new语句。它像Java构造对象时那样调用。像下面:

new Student("Sam", 18);

而上面语句实际做的工作,用JavaScript描述大致就是:

var obj = {};
Student.call(obj, "Sam", 18);
obj.i_was_build_by = Student;
return obj;

这里特别注意的是代码的第三行。我们不再是通过增加一个class属性来区分对象的类,而是通过加入一个i_was_build_by属性,它引用了构造函数Student。这个相当于前面的class属性,不过它引用的不是一个单纯的字符串,而是一个函数了。这样也行。我们也可以类似的判断一个对象是否属于某个类:

s instanceof Student //等效于 s.i_was_build_by == Student

我写出这么一个奇葩的名字,是不想误导读者。如果要深追究,JavaScript当中不是通过这种方式来区别对象的,其机制要稍微复杂些,不过大体思想是一致的。

让方法只定义一次

我们看到改动后的代码,其依然有个不足之处。在基于类模板的语言中,方法是属于类的,只需要定义一次。而在我们的版本中,方法是属于对象的,其在每次Student函数调用过程当中都会被定义一次。且不说带来的内存消耗吧。这样离看上去像Java也是差了些。所以这里又要做些改动,使得方法只需要定义一次。

思路就是新创建的对象要保持一个对象引用,这个对象囊括了对象所属类的方法集合。首先,这个引用的名字是prototype;其次,它的来源是构造函数同为名prototype的引用;最后,所有在本对象中找不到的方法,都推到prototype中去查找。例如,我们要把之前的案例像下面这样写:

function Student(name, age) {
this.name = name;
this.age = age;
} Student.prototype.toString = function() {
return this.name + " " + this.age;
}; var s = new Student();
//s.prototype == Student.prototype;
//s.toString() == s.prototype.toString.call(s);

解释:Student函数本身有个属性prototype。通过new Student()语句构造的s对象,它的prototype属性指向了函数Student的prototype属性。最后当调用s.toString()时,由于s中不存在toString属性,继而跳到prototype对象中去查找。就好像prototype当中的属性是自己的属性一样。

那么真正地new Student("Sam", 18)语句执行逻辑可以总结如下:

var obj = {};
Student.call(obj, "Sam", 18);
obj.i_was_build_by = Student;
obj.prototype = Student.prototype;
return obj;

通过这种方式,我们可以只需要在prototype处定义方法一次即可;另外,prototype也可以定义类的共有属性。这就是prototype处的作用。下面我们还会看到,通过prototype链的方式,它也开拓了通往继承之门的道路。

四、继承并不神秘,它就是prototype链

真的快要写完了。也许在JavaScript中,最值得着墨的地方就是继承了。不过我写的有些累了,这里不再多提了。

其思路就是扩展prototype下去。我们之前提过,如果一个对象的属性找不到,就会在它的prototype引用中去找;如果在prototype引用中还找不到呢?那么就会在prototype引用的prototype引用中再去找,一直到找到为止或者prototype引用为空。

但这与继承有什么联系呢?事实上,通过巧妙地构造prototype链,就可以实现继承的效果了。不便说了,上例子吧:

function Animal() {}

function Dog() {}

Dog.prototype = new Animal();

这便实现了继承的魔法。乍一看也许没明白,需要拆解开:

let animal = new Animal();
animal.prototype == Animal.prototype; let Dog.prototype = animal; let dog = new Dog();
dog.prototype == Dog.prototype == animal;

上面的例子显示了,如果新建一个dog对象,它的prototype对象(暂且取个中间变量)为animal,而animal对象的prototype对象就会回到Animal的prototype中去。对于dog的某个方法调用,它首先在animal中寻找(这个是Dog的prototype);如果找不到,就会在animal的prototype中寻找(这个是Animal的prototype)。这样,我们不仅可以调用Dog.prototype中定义的方法(这是子类的方法),也可以调用Animal.prototype的方法(这个是继承于父类的方法)。这样操作便实现了继承。

五、总结

JavaScript按照这种思路就创造得差不多了。这种思路差不多就是JavaScript面向对象的一种概述了。而实际上,JavaScript真正地内部机制比起这个要复杂一些;不过我相信它也有自己的考量。总之,JavaScript有着自己的面向对象思想,又要引入传统的基于类模板的面向对象概念进来,就变成了现在这样了。

(二)我的JavaScript系列:JavaScript面向对象旅程(下)的更多相关文章

  1. JavaScript 系列--JavaScript一些奇淫技巧的实现方法(二)数字格式化 1234567890转1,234,567,890;argruments 对象(类数组)转换成数组

    一.前言 之前写了一篇文章:JavaScript 系列--JavaScript一些奇淫技巧的实现方法(一)简短的sleep函数,获取时间戳 https://www.mwcxs.top/page/746 ...

  2. JavaScript 系列--JavaScript一些奇淫技巧的实现方法(一)简短的sleep函数,获取时间戳

    一.前言 有些东西很好用,但是你未必知道:有些东西你可能用过,但是你未必知道原理.实现一个目的有多种途径,俗话说,条条大路通罗马.发散一下大家的思维以及拓展一下知识面. 二.实现一个简短的sleep函 ...

  3. JavaScript系列--JavaScript数组高阶函数reduce()方法详解及奇淫技巧

    一.前言 reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值. reduce() 可以作为一个高阶函数,用于函数的 compose. reduce()方 ...

  4. JavaScript 系列--JavaScript一些奇淫技巧的实现方法(三)数字取整,数组求和

    一.前言 简短的sleep函数,获取时间戳:https://www.mwcxs.top/page/746.html 数字格式化 1234567890 --> 1,234,567,890:argr ...

  5. javascript系列-class10.DOM(下)

    1.node节点(更详细的获取(设置)页面中所有的内容)         根据 W3C 的 HTML DOM 标准,HTML 文档中的所有内容都是节点:   元素是节点的别称,节点包含元素当然节点还有 ...

  6. (一)我的Javascript系列:Javascript的面向对象旅程(上)

    今宵酒醒何处,杨柳岸,晓风残月 导引 我的JavaScript系列文章是我自己对JavaScript语言的感悟所撰写的系列文章.现在还没有写完.目前一共出了下面的系列: (三)我的JavaScript ...

  7. JavaScript面向对象旅程(下)

    JavaScript面向对象旅程 剪不断,理还乱,是离愁. 前面已经提到过新语言开发的两个步骤,分别是:一.定义基本的数据类型,完善结构化编程语言的设计:二.为函数类型绑定this的概念,好在对象的方 ...

  8. 前端开发:面向对象与javascript中的面向对象实现(二)构造函数与原型

    前端开发:面向对象与javascript中的面向对象实现(二)构造函数与原型 前言(题外话): 有人说拖延症是一个绝症,哎呀治不好了.先不说这是一个每个人都多多少少会有的,也不管它究竟对生活有多么大的 ...

  9. javascript系列之DOM(二)

    原文:javascript系列之DOM(二) 原生DOM扩展 我们接着第一部分来说,上文提到了两种常规的DOM操作:创建文档片段和遍历元素节点.我们知道那些雨后春笋般的库,有很大一部分工作就是提供了一 ...

  10. JavaScript 系列博客(二)

    JavaScript 系列博客(二) 前言 本篇博客介绍 js 中的运算符.条件语句.循环语句以及数组. 运算符 算术运算符 // + | - | * | / | % | ++ | -- consol ...

随机推荐

  1. PCL推荐的命名规范(2)

    博客转载自:http://www.pclcn.org/study/shownews.php?lang=cn&id=210 函数/成员函数命名 函数和类的成员函数的命名应该采用camelCase ...

  2. <c和指针>学习笔记3之操作符,表达式与指针

    1 操作符 (1)移位操作符 左移<<:值最左边的几位丢弃,右边多出来的几个空位用0补齐 01101101 011(丢弃)01101000(后面三位补0) 右移>>: 算术左移 ...

  3. 【机器学习】文本分类——朴素贝叶斯Bayes

    朴素贝叶斯主要用于文本分类.文本分类常见三大算法:KNN.朴素贝叶斯.支持向量机SVM. 一.贝叶斯定理 贝叶斯公式思想:利用已知值来估计未知概率.已知某条件概率,如何得到两个事件交换后的概率,也就是 ...

  4. java之字符串转换

    参考http://how2j.cn/k/number-string/number-string-parse/317.html 数字转字符串 方法1: 使用String类的静态方法valueOf 方法2 ...

  5. textarea的style="resize:none;"

    <textarea class="form-control" id="gryj" rows="3" maxlength="3 ...

  6. 8. php回调后门

    中国菜刀下载,基于原版中国菜刀优化版20160309. 下载地址: http://pan.baidu.com/s/1jHoJxHW China chopper http://pan.baidu.com ...

  7. 51Nod - 1092 回文字符串(添加删除字符LCS变形)

    回文字符串 回文串是指aba.abba.cccbccc.aaaa这种左右对称的字符串.每个字符串都可以通过向中间添加一些字符,使之变为回文字符串. 例如:abbc 添加2个字符可以变为 acbbca, ...

  8. MS SQL的CASE...WHEN...THEN...END语法

    根据多个可能的答案检查一个值或变量. 举例说明: SELECT [type],CASE [type] WHEN 'TT' THEN 'TYPE_TABLE' WHEN 'FN' THEN 'SQL_S ...

  9. uoj#38. 【清华集训2014】奇数国(线段树+数论)

    传送门 不难看出就是要先求区间积,再求这个区间积的\(\varphi\) 因为\(\varphi(x)=x\times\frac{p_1-1}{p_1}\times\frac{p_2-1}{p_2}\ ...

  10. Bootstrap 栅栏布局中 col-xs-*、col-sm-*、col-md-*、col-lg-* 区别及使用方法

    (1)概括          一句话概括:根据显示屏幕宽度的大小,自动的选用对应的类的样式. (2)关键字段        1.col是column简写:列: 2.xs是maxsmall简写:超小,  ...