说到对象,我想到一个成语叫作谈虎色变,对象应该说是javascript中最难得一部分,原因呢,首先,js作为函数式编程语言,对象的实现方式跟java,c,c++等面向对象设计语言不一样;其次js面向对象编程属于高级程序员应该掌握的,对于初学者来讲,没有实践基础,凭空理解更是难上加难。不过不用担心,我也不怎么会,我们就一起来探讨一下吧。
我们举个例子,大千世界,人心难测。每个人都是独立的,有自己的思维,性别,年龄,会跑,会跳等等,人就是一个单位,一个整体,然后人与人之间才会有血缘关系,社会关系。试想,一个人都不完整,还谈什么其他东西呢?所以我们的首先任务就是“造人”,造人的方式的有很多种,当然肯定不是你想的那种,哈哈!好,接下来,开始造人吧!
工厂模式的原理:在函数内部创建object对象,对象属性由参数指定,方法也挂在object对象上,最后返回这个object对象。相当于Person函数是一个造人工厂,一下子造了Tom,Linda两个人。但是缺点是不知道这两个人什么类型的,你会说都是object啊,对啊,你回答这个答案就像我问你你现在在哪?你说你在地球上一样的道理。我们应该知道,js里所有事物都是object类型,所以为了搞明白这两个人具体的类型,我们另有他法,那就是构造函数模式。
function Person(name,sex){ var o = new Object(); o.name = name; o.sex = sex; o.say = function(){ console.log(this.name + ' is '+ this.sex) }; return o;}var Tom = Person('Tom','male');var Linda = Person('Linda','female');构造函数模式相比工厂函数就是知道Tom和Linda这两个人是Person类型,原理是new关键字默认执行了以下操作: 1.创建一个全新的对象 2.这个对象会被执行[[PRototype]]连接原型 3.函数调用中的this会绑定到新对象 4.如果函数没有返回其他对象,那么new 构造就会自动返回这个新对象 注意:这里我特意加粗这句话,因为文末讲的寄生构函数模式的理解需要仰仗这句话。先透个底,如果函数像工厂函数那样返回了对象,那么new关键词也就不会执行默认操作。
function Person(name,sex){ this.name = name; this.sex = sex; this.say = function(){ console.log(this.name + ' is '+ this.sex); }}var Tom = new Person('Tom','male');var Linda = new Person('Linda','female');上述的原型链图示为:
Person.prototype指向原型对象,而原型对象的constructor属性指向构造函数Person,Tom和Linda实例继承了原型对象的constructor属性,所以有下面的等式
console.log(Tom.constructor === Person.prototype.constructor && Tom.constructor === Person);//tureconsole.log(Linda.constructor === Person.prototype.constructor && Linda.constructor === Person);//tureJavascript还提供了instanceof,验证实例与构造函数的关系
console.log(Tom instanceof Person);//trueconsole.log(Linda instanceof Person);//true构造函数有个弊端,就是每一次new一个对象的时候,其实都创建了各自的属性和方法,这些属性和方法是重复的,完全没必要嘛,代码量多,又浪费内存。原型模式解决了这个问题,将公用方法和不变的属性挂在原型对象上。
//构造函数模式new的实例,相同的属性和方法是不一致的,验证一下console.log(Tom.name === Linda.name);//falseconsole.log(Tom.sex === Linda.sex);//falseconsole.log(Tom.say === Linda.say);//false//不对就对了,下面是原型模式function Person(){}Person.prototype.name = 'Tom';Person.prototype.sex = 'male';Person.prototype.say = function(){ console.log(this.name + ' is '+ this.sex);};var Tom = new Person();var Linda = new Person();原型链图示为:
实例属性或方法的访问过程是一次搜索过程: 1.首先从对象实例本身开始,如果找到属性就直接返回该属性值; 2.如果实例本身不存在要查找属性,就继续搜索指针指向的原型对象,在其中查找给定名字的属性,如果有就返回; 基于以上分析,原型模式创建的对象实例,其属性是共享原型对象的;但也可以自己实例中再进行定义,在查找时,就不从原型对象获取,而是根据搜索原则,得到本实例的返回;简单来说,就是实例中属性会屏蔽原型对象中的属性;
接下来看一下这些属性和方法相等吗?
console.log(Tom.name === Linda.name);//trueconsole.log(Tom.sex === Linda.sex);//trueconsole.log(Tom.say === Linda.say);//truejavascript提供一些验证实例和原型对象关系的方法
//isPrototypeOf用来判断,某个原型对象和某个实例之间的关系console.log(Person.prototype.isPrototypeOf(Tom)); //trueconsole.log(Person.prototype.isPrototypeOf(Linda)); //true//hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性console.log(Tom.hasOwnProperty("name")); // falseconsole.log(Linda.hasOwnProperty("name")); // false//in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性console.log("name" in Tom); // trueconsole.log("name" in Linda); // true简称组合模式,构造函数实例好比私有制,原型模式实例好比公有制,那么组合模式就是以公有制为核心,私有制并行的模式,佩服我历史学得真好!所以自己的属性就不要挂在原型对象上,只有共有属性和方法才挂在上面。
function Person(name,sex){ this.name = name; this.sex = sex;}Person.prototype = { //原型字面量方式会将对象的constructor变为Object,此外强制指回Person constructor: Person, say: function(){ console.log(this.name + ' is '+ this.sex); }}var Tom = new Person('Tom','male');var Linda = new Person('Linda','female');原型链示图:
做些测试吧!
console.log(Tom.name);//Tomconsole.log(Linda.name);//Lindaconsole.log(Tom.say === Linda.say);//true组合模式私有属性和共有属性及方法是分离的,如果能够放在一个构造函数里,那就真的和对象相差无几了。
function Person(name,sex){ this.name = name; this.sex = sex; if(typeof this.say != 'function'){ Person.prototype = { constructor: Person, say: function(){ console.log(this.name + ' is '+ this.sex); } } }}var Tom = new Person('Tom','male');var Linda = new Person('Linda','female');来验证一下吧!
console.log(Tom.name);//Tomconsole.log(Linda.name);//Linda//===>想想为什么第一个Tom.say返回undefined?我似懂非懂Tom.say;//undefinedLinda.say;//function还记得讲构造函数模式的时候,new的实质吗?温习一下: new的实质是执行了以下操作 1.创建一个全新的对象 2.这个对象会被执行[[prototype]]连接原型 3.函数调用中的this会绑定到新对象 4.如果函数没有返回其他对象,那么new 构造就会自动返回这个默认对象 注意:如果返回了其他对象,那么new关键词也就不会执行默认操作。
现在有个疑问:以上以new创建实例的模式都没有返回新对象,而是依靠new的默认操作创建的对象实例。但是,but,but,new默认返回的对象是object类型,如果我现在要返回array或者其他对象,new就不能再执行其默认操作了,所以就要用return语句重写构造函数,返回期望的对象类型。
function SpecialArray(){ var values = new Array(); values.push.apply(values, arguments); values.toPipedString = function(){ return this.join("|"); }; return values; }var a = new SpecialArray(2,6,8,9,4);a.toPipedString();//2|6|8|9|4上面是网上随便找的一段关于创建数组对象的例子,我们还是改写我们的例子
function Person(name,sex){ var o = new Object(); o.name = name; o.sex = sex; o.say = function(){ console.log(this.name + ' is '+ this.sex) }; return o;}//与工厂模式的区别就是调用的时候加上了new关键字,工厂模式直接调用函数var Tom = new Person('Tom','male');var Linda = new Person('Linda','female');那么就有人问了,这寄生构造函数模式又产生什么结果呢?其实答案和工厂模式输出结果一模一样。那有了工厂模式了,寄生构造模式是不是多余的呢?借用高程书上的原话: 书上原话:除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。构造函数在不返回值的情况下。默认会返回新对象实例。而通过在构造函数的末尾添加一个return语句,可以重写调用构造函数时返回的值。
对象涉及封装和继承,继承放在后一片文章去讲。弄了这么一通,对象封装也就6个模式,寄生构造函数模式跟工厂模式没有什么大区别,而且我们经常用的是组合模式和动态原型模式。所以实践看起来是要比学习单纯许多。哈哈
新闻热点
疑难解答