原型和原型链是JavaScript中的难点也是重点,这里建议先阅读第六章中关于原型和原型链的部分再来阅读此处。
原型
如上图所示,数组a在创建时生成一个__proto__对象,该对象指向构造函数Array的prototype对象。同时Array.prototype.__proto__指向Object.prototype,所以a.__proto__.__proto__也指向Object.prototype。
如上图所示,创建构造函数B以及它的实例c,则c.__proto__指向构造函数B的原型prototype,所以两者是相等的。
原型链
前面提到过__proto__是一个指针对象(属性),它指向的是构造它的对象的构造函数(对象)的prototype属性(对象)。几乎所有的javascript对象都有__proto__这样一个指 针包括Object。
如上图所示,可以看到a = 1;实际相当于a = new Number(1);,所有内建对象及Object的__proto__指向的都是一个匿名函数,可以认为它们其实也是function的一个实例。并且我们知道Number.prototype对象(即a.__proto__)是没有toString()方法的,但是a却可以调用该方法,这就是原型链的作用。
如上图所示,a.__proto__.__proto__指向的是Object对象,而Object对象是有toString()方法的,所以对象a能调用toStirng()方法。a的原型链就是由Number.prototype和Object.prototype成,当a访问一个方法或属性时,它会先在自身查找,然后再沿原型链找,找到则调用,没找到报错。
如果给Number.prototype添加toString()方法,则a在查找到原型链的Number.prototype(即a.__proto__)上时发现了toString()方法,则会使用该方法而不是继续沿着原型链查找,并且几乎所有的对象的原型链的终点都是Object.prototype。
类和构造函数
从上图可以看到,创建了构造函数A,使用new关键字创建对象x和不使用new创建对象y,当不使用new时并不会显示地报错,但是构造的对象y并不是A的实例,而是变成了undefined,同时x中还包含名为a的属性,其值为123。
如上图所示,可以得到对象x和对象y属于类Test,因为它们继承自同一个原型对象Test.prototype,同时给Test的原型对象添加add()方法,则对象x和y都可以调用该方法。
从上图可以看出类Test的原型对象的constructor指向Test类本身。同时,可以看到构造函数的原型中存在预先定义好的constructor属性,这就意味着对象继承的constructor属性指代它们的构造函数,由于构造函数是类的"公共标识",因此这个constructor属性为对象提供了类。需要注意的是,如果重写预定义的Test.prototype对象,这个新定义的原型对象并不会包含constructor属性,因此需要手动添加。
模块
模块通常是指编程语言所提供的代码组织机制,利用此机制可将程序拆解为独立且通用的代码单元。所谓模块化主要是解决代码分割、作用域隔离、模块之间的依赖管理以及发布到生产环境时的自动化打包与处理等多个方面。
- 可维护性: 因为模块是独立的,一个设计良好的模块会让外面的代码对自己的依赖越少越好,这样自己就可以独立去更新和改进。
- 命名空间: 在 JavaScript 里面,如果一个变量在最顶级的函数之外声明,它就直接变成全局可用。因此,常常不小心出现命名冲突的情况。使用模块化开发来封装变量,可以避免污染全局环境。
- 重用代码: 有时候会喜欢从之前写过的项目中拷贝代码到新的项目,这没有问题,但是更好的方法是,通过模块引用的方式,来避免重复的代码库。