ES5及以前的几种对象创建方式
面向对象编程,有多种实现模式,JS的原型链也可以算是其中一种。当我初学JS的时候,第一次在JS高程上看到JS创建对象的方式,觉得十分有意思,因为之前接触的各种语言,你要创建一个对象,只需要一个class,然后就可以尽情的去搭建你的世界了,之后使用的时候,new一下,你就能轻松创建好一个你设计好的类的实体对象。但是高程上对一个“简单的”创建对象却给出了好几种不同的方式。说实话,当时看的有点晕晕乎乎的,什么工厂模式,构造函数模式,组合模式,原型模式,寄生构造什么的,一大堆,死记硬背下来也不知道哪个好用。
后来写了一段时间的JS,再回头看其创建对象。我开始想一个问题,这么多种模式的最终目的是什么。很简单就是创建一个对象并返回它,那么我看书的时候,只要抓住一个点,那些各种各样的模式,返回给了我什么,就可以抓住问题的关键。然后按图索骥,一步一步往上走,看这返回给我的东西是怎么加工成的,跟着书去分析,不要把代码当作一个黑盒一样的东西,好像我照着书这么配置一下,然后调用一下突然就有了一个对象。
当然,可能只有我才在初学的时候,对创建对象理解的很模糊,一是我以过去的经验来套用的JS的创建,因为class这个关键字,就有点像黑盒,我写了一个类,配置好一些东西,然后我new一下,我就得到了一个和我配置的类一样的对象。然后我面对JS这个可以说有些透明的对象创建过程,有点懵逼。其次可能当时看书也是不太细致,没想着仔细理解,想着先记下来再说,导致概念模模糊糊的。
在学习创建对象的各种方法时,有个前置知识,那就是JS中,new 这个关键字,到底有什么用。
其实说来也简单,当你使用new 去运行一个函数时,它干了这么几件事:
-创建了一个临时对象,并让函数的this指向了这个临时对象
-将所创建的临时对象的原型(proto)绑定为你这个函数的原型(prototype)
-返回所创建的这个临时对象,假如你显式的返回了一个对象,则不返回临时对象,但是如果显式返回的不是对象,仍会返回这个临时对象。
明白了new的作用有利于理解所谓构造函数,返回了什么东西。
那么既然new只干了这么几件事情,我完全可以不用new,写一个普通的函数就能为我创建一个对象。
function createO(){
var o = {};
o.a = 1;
return o;
}
var newObj = createObj();
如上面的函数,我运行这个函数时,创建了一个对象o,并将其作为返回值。这样我调用这个函数时就创建了一个新的o对象,并赋值给newObj,这个o对象只拥有一个属性a。看起来跟其他面向对象语言的class之类的,完全不沾边,但是它成功实现了创建一个对象这件事情。那么每当我想要一个新的o对象时,我就调用一下这个函数就可以。这就是所谓的工厂模式。
function O(){
this.a = 1;
}
var newObj = new O();
当我不想自己去手动创建对象,返回对象等等一系列工作的时候,我用一下new操作,如上面的代码,把创建对象和返回对象的工作都省略了,交给new来完成,这就是构造函数模式。另外,虽然不是语法规定,但是我们约定俗成的让构造函数首字母大写,以便区分。
以上两种方式理论上完全满足需求了,但是我们在重复构建一个对象的时候,有些东西是可以复用的,并不需要每次都创建一个新的实体,去占用我们的内存空间。假设我现在要一个getA的方法去返回a的值,显然我并不需要每个创建出来的O对象都有一个属于他自己的get方法,它们完全可以共用同一个,于是就有了原型模式。
function O(){
this.a = 1;
}
O.prototype.getA = function(){
return this.a;
}
当然按JS高程的说法,我上面的写法叫原型模式和构造函数的组合。不过意思都差不多,引入原型的目的就是让那些可以共用的属性以及方法不用重复创建,所以完全单独的构造函数模式或者原型模式用处都有局限。只要像上面代码一样,需要每个对象自己一套放在构造函数里面,可以公用的,放在prototype原型里面就可以。
还有所谓动态原型模式,其实就是在修改prototype原型对象前,先做个逻辑判断。
虽然构造函数和原型的组合是最常用的,但是还是有些需求满足不了,那就是私有变量的问题。你所创建的所有属性、方法,都是可以在外部直接访问到,可以随意操作的。这样无法避免的会带来一些安全性问题,不过也有解决方案。
之前讲构造函数模式的时候,有提到,虽然new会默认帮你返回一个对象,但是其实你自己返回一个你想要的对象。
function O(){
var o = new Object();
o.a = 1;
o.getA = function(){
return this.a;
}
return o;
}
如代码所示,我调用这个构造函数时,创建了一个o对象,并返回。这种模式被称为寄生构造函数模式。但其实很容易看出来,我假如不用new,不把它当构造函数,它其实跟工厂模式是一样的。
那为什么这种模式可以解决私有变量的问题呢?我们知道,js有个特性,叫闭包。就是一个函数,引用的它自身作用域以外的变量时,就构成了一个闭包。然后如果这个函数的引用被留存下来了,一直“活”着,那么他所引用的那些量也不会被销毁。
function O(){
var o = new Object();
var a = 1;
o.getA = function (){
return a;
}
return o;
}
如上面的代码,本来当构造函数O执行完毕时,他的作用域出栈了,所有的变量应该被销毁,但是返回的对象o的一个方法getA引用了本该被销毁的变量a。这样,只要我们没有把构造函数所返回的对象o给销毁掉,就会一直对a保持这引用,让其一直存活。这个时候a不再是返回的o对象的一条属性,我们无法再通过 o.a=2 这种形式直接对其进行操作,但是我们可以通过o的各种方法去获取a,去改变a,去使用a。这个时候,变量a就达到了类似私有属性的效果。
这种模式被称为稳妥构造函数模式,但是显而易见,这种模式抛弃了原型模式那种共用方法属性的优点,我们在使用的时候可以酌情选择。
ES6带来的改变
ES6对于对象创建的改变,其实只是一个语法糖,其实质并没有什么变化,底层仍然是构造函数与原型模式的组合模式。我看过class关键字的一些特性,基础语法不多说,其实例方法,静态方法其实就是在构造函数中创建方法和在原型链上创建方法的区别。私有变量,私有方法这种只能在命名上做个约定,有提案但是并未作为新规则实现。所以要用私有变量时,可能还要回复稳妥模式,用ES5的写法。
总的来说,ES6的class让JS的OO编程和java,c++等语言在写法上更加相近了,上手也更方便了一些,不需要理解一大堆原型链相关的东西,底层都被封装了。这样虽然写起来可能更爽了一些,但是我觉得JS的原型链相关知识,JS对象具体的创建过程,了解下没有坏处。
剩下的,class具体的语法细节就不多说了,大家看文档就好。




近期评论