观看者:有一定的javascript基础的,会用或者了解向对象但是不知道原理的。
目标:以执行效率最高的方式实现javascript的面向对象模式。
实现目标方式:从最基础的实现开始一步一步优化到最后的实现方式。请运行或者读懂每一段代码。
一,创建对象:
var people1 = new Object();//只有对象上才能添加属性 people1.name = 'fanlizhi'; people1.sayName = function(){alert(people1.name);} var people2 = new Object(); people2.name = '小明'; people2.sayName = function(){alert(people2.name);} people1.sayName();//fanlizhi people2.sayName();//小明
缺点:最先想到的方式,实例都需要重新给属性如果n个实例,代码量大。
二,工厂模式:
function createPerson(name){ var o = new Object(); o.name = name; o.sayName = function(){ alert(this.name); } return o; } var people1 = createPerson('fanlizhi'); var people2 = createPerson('小明'); people2.sayName();//小明
进步:较上面相比有统一的入口方法createPerson并且n个实例代码量明显减少。
缺点:不能解决对象识别的问题,我不知道people1和people2都是属于什么对象的。
三,构造函数模式:
function Person(name){ this.name = name; this.sayName = function(){ alert(this.name); } } var people1 = new Person('fanlizhi'); var people2 = new Person('小明'); people1.sayName();//fanlizhi people2.sayName();//小明 //constructor:标识对象类型的 alert(people1.constructor == Person);alert(people2.constructor == Person);//true //instanceof:检测对象类型是否属于XXX alert(people1 instanceof Object);alert(people1 instanceof Person);alert(people2 instanceof Object);alert(people2 instanceof Person);//true
进步:更加靠近面向对象语言形式了, 用到new关键字和“类名”(方法名Person)开头大写。直接将this指向Person而且不需要return对象。
缺点:因为每次实例化的时候都会执行Person方法,所以每个实例不仅有自己独有的name同时,也将有自己独有的sayName方法(不是同一个方法的引用)。如下面的例子:
alert(people1.sayName == people2.sayName); //false
因此浪费了资源,我们只需要调用同一个方法就行了而不像现在调用的是两个sayName方法。
四;提出构造函数中的方法:
function Person(name){ this.name = name; this.sayName = sayName; } function sayName(){ alert(this.name); } var people1 = new Person('fanlizhi'); var people2 = new Person('小明'); alert(people1.sayName == people2.sayName);//true
进步:people1和people2中的方法终于是引用的同一个方法了。
缺点:大家都知道,sayName是window的一个方法可以用window.sayName()调用,给window添加方法很乱,所以封装性不好。
五,原型模式:
首先我需要说明一下什么是prototype,要不然在代码里面会显得突兀,prototype:当实例对象的时候,对象会自动生成一个prototype属性,他是一个指针指向Person原型,我们可以直接理解为Person.prtotype = Person原型 = 可以共享的属性和方法的一个载体。
function Person(name){ this.name = name; } Person.prototype.sayName = function(){ alert(this.name); } var people1 = new Person('fanlizhi'); var people2 = new Person('小明'); alert(people1.sayName == people2.sayName);//true
进步:由于上一种方式缺点在于指针指的是window,这里用prototype指向了Person,更干净了构造函数中传值的name可以实例化name为不同的对象,所以放到Person下(即:构造函数下),而sayName只需要调用公共的方法不需要每个对象有自己单独的实例,所以放到prototype中。
我们已经走完实现过程了,这还早,下面才是重头戏,我们一起来分析一下上面的代码。
深入理解原型:
先看一个例子:这是我用firebug查看people2中所含有的属性,还是原型模式中的代码。
__proto__:当实例化对象people1的时候解释器内部的一个关联关系。换句话说就是people1与Person.prototype有一个连接__proto__,isPrototypeOf就是判断这个连接的方法(后面会给出例子)。我们还有个链接不知道大家忘记没有,就是constructor,constructor其实就是对象实例化的时候people1与Person的关系,people1.constructor指向Person,其实这个constructor是来自于People.prototype.constructor,这个实例化时候内部解释器自动加上的默认属性。
alert(Person.prototype.isPrototypeOf(people1));//true
注意:我要强调一点就是__proto__是实例和Person.prototype之间的关系,而constructor是实例和Person之间的关系。
下面我们聊一下关于优先级的问题,先看代码:
function Person(name){ this.name = name; } Person.prototype.sayName = function(){ alert(this.name); } var people1 = new Person('fanlizhi'); people1.sayName = function(){ alert(this.name+' hello'); } people1.sayName();//fanlizhi hello delete people1.sayName; people1.sayName();//fanlizhi从这里我们发现一个访问优先级的问题:当我们第一次调用people1.sayName()时他先找实例people1中是否含有sayName()如果有就调用并返回当我们删除了people1中的sayName的时候,再次调用people1.sayName(),这次发现实例people1中没有sayName(),所以他就找原型里面是否有当发现原型里面有的时候就调用原型里面的sayName()。在这里我得讲两个方法hasOwnProperty() 和 in 这个有助于我们对优先级的了解,先看例子:
function Person(){ } Person.prototype.name = 'fanlizhi'; Person.prototype.sayName = function(){ alert(this.name); } var people1 = new Person(); alert(people1.hasOwnProperty("name"));//false alert("name" in people1);//true people1.name = '小明'; alert(people1.hasOwnProperty("name"));//true alert("name" in people1);//true
看例子基本就懂了,hasOwnProperty 从名字来看“有自己独有的属性”第一次调用的时候为false,因为name不是独有的而是公共的后来通过people1.name = '小明'; 赋值给他一个独有的name,所以后来为true, 而in操作符,只要能访问到people1.name,不论是独有的还是公共的都返回true。
我们经常会看到这样的例子:function Person(){ } Person.prototype = { name:'fanlizhi', sayName:function(){ alert(this.name); } }我们用一段代码来辅助我们对上面方式的分析:
function Person(){ } var people1 = new Person(); alert(people1.constructor);//Person() Person.prototype = { name:'fanlizhi', sayName:function(){ alert(this.name); } } var people2 = new Person(); alert(people2.constructor);//Object() people1.sayName();//error people2.sayName();//fanlizhi
当我们实例化people1的时候,默认创建的constructor会取到prototype的指向,是隶属于Person()的所以alert(people1.constructor);//Person();当我们实例化people2的时候,也会取到prototype的指向,但是这次我们是重写了Person.prototype所以打印的是Object(),至于为什么,可以理解成var o = {} ,这是一个Object申明的过程。people1.sayName();//error : 由于实例化的时候使用的是默认的prototype,后来改写了Person.prototype所以__proto__被破坏找不到Person.prototype和实例关联,所以error,而people2则是在实例化的时候就调用的是后来重写的Person.prototype,并建立了关联所以调用时候能根据连接找到属性,而没有报错。
以上便是我对面向对象的理解,通过文字和代码传达给大家,希望大家多多提问,一起来讨论。
最后有个思考题:
function Person(){ } Person.prototype = { constructor:Person, name:'fanlizhi', sayName:function(){ alert(this.name); } }为什么要在原型中加入constructor:Person呢?