前言:本文主要总结一下javascript建立对象的方法、原型、原型链和继承,可是先从建立对象的几种方法开始,延伸到原型模式建立对象以及其它模式。继承原本想一块写了,发现太多内容了,放到下里总结。javascript
建立对象最基本的两个方法是:Object构造函数
和对象字面量
。java
//Object构造函数方式 var person = new Object(); person.name = "Jack"; person.age = 12; person.sayName = function(){ alert(this.name); }; //字面量方式 var person = { name: "Jack", age: 14, job: "码农", sayName: function(){ alert(this.name); } };
上述两个基本方法的缺点是:使用同一个接口建立不少对象,会产生大量的复制代码。针对这个缺点,看下面
原理是用函数来封装以特定接口建立对象的细节数组
function createPerson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var person1 = createPerson("Jack",15,"码农"); var person2 = createPerson("rose",12,"程序媛");
函数createPerson能接收参数构建一个包含全部属性的对象,而且能够用不多的代码不断的建立多个对象,可是因为它被函数所封装,暴露的接口不能有效的识别对象的类型(即你不知道是Object仍是自定义的什么对象)。浏览器
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; } var person1 = new Person("Jack",15,"码农"); //满满的java复古风 var person2 = new Person("Rose",15,"程序媛");
与工厂模式相比,构造函数模式用Person()函数代替了createPerson()函数,而且没有显示的建立对象,直接把属性和方法赋值给了this对象。app
要建立Person的实例,必须使用new关键字。
函数
person1和person2都是Person的实例,这两个对象都有一个constructor(构造函数)属性,该属性指向Person。 person1.constructor == Person; //true
this
person1便是Person的实例又是Object的实例,后面继承原型链会总结。spa
//当作构造函数使用 var person1 = new Person("Jack",15,"码农"); person1.sayName(); //"Jack" //当作普通函数使用 Person("Jack",16,"码农"); //注意:此处添加到了window window.sayName(); //"Jack" //在另外一个对象的做用域中调用 var o = new Object(); Person.call(o,"Jack",12,"码农"); o.sayName(); //"Jack"
第一种当作构造函数使用就很少说了prototype
当在全局做用域中调用Person("Jack",16,"码农");
时,this对象老是指向Global对象(浏览器中是window对象)。所以在执行完这句代码后,能够经过window对象来调用sayName()
方法,而且返回“Jack”。指针
最后也可使用call()
或者apply()
在某个特殊对象的做用域中调用Person()
函数
在(3)构造函数模式的代码中,对象的方法sayName的功能都同样,就是alert当前对象的name。当实例化Person以后,每一个实例(person1和person2)都有一个名为sayName的方法,可是两个方法不是同一个Function实例
。不要忘了,js中函数是对象
,因此每一个实例都包含一个不一样的Function实例,然而建立两个功能彻底同样的Function实例是彻底没有必要的
。所以能够把函数定义转移到构造函数外。
以下代码:
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); } //实例化对象 var person1 = new Person("Jack",15,"码农"); //满满的java复古风 var person2 = new Person("Rose",15,"程序媛");
可是这样依然存在问题:
为了让Person的实例化对象共享在全局做用域中定义的同一个sayName()
函数,咱们把函数sayName()
定义在全局做用域中,并经过指针sayName指向构造函数,因此在全局做用域中的sayName()
只能被特定对象调用,全局做用域名不符实,且污染全局变量。
而且若是对象须要不少种方法,那么就要定义不少全局函数,对于对象就没有封装性,而且污染全局。
js不一样于强类型语言的java,java建立对象的过程是由类(抽象)到类的实例的过程,是一个从抽象到具体的过程。
javascript则不一样,其用原型建立对象是一个具体到具体的过程,即以一个实际的实例为蓝本(原型),去建立另外一个实例对象。
因此用原型模式建立对象有两种方式:
1.Object.create()方法
Object.create:它接收两个参数,第一个是用做新对象原型的对象(即原型),一个是为新对象定义额外属性的对象(可选,不经常使用)。
var Person = { name:"Jack", job:"码农" }; //传递一个参数 var anotherPerson = Object.create(Person); anotherPerson.name //"Jack" //传递两个参数 var yetPerson = Object.create(Person,{name:{value:"Rose"}}); yetPerson.name; //Rose
2.构造函数方法建立对象
任何一个函数都有一个prototype属性(是一个指针),指向经过构造函数建立的实例对象
的原型对象
,原型对象可让全部对象实例共享它所包含的属性和方法。
所以没必要在构造函数中定义对象实例的信息,而是将这些属性和方法直接添加到原型对象中,从而被实例对象多继承(继承后面总结)
//第一步:用构造函数建立一个空对象 function Person(){ } //第二步:给原型对象设置属性和方法 Person.prototype.name = "Jack"; Person.prototype.age = 20; Person.prototype.job = "码农"; Person.prototype.sayName = function(){ alert(this.name); }; //第三步:实例化对象后,即可继承原型对象的方法和属性 var person1 = new Person(); person1.sayName(); //Jack var person2 = new Person(); person2.sayName(); //Jack alert(person1.sayName == person2.sayName); //true
person1和person2说访问的是同一组属性和同一个sayName()函数。
只要建立一个函数,就会为该函数建立一个prototype属性,这个属性指向函数的原型对象。
全部原型对象都会自动得到一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。
当调用构造函数建立一个新的实例对象后,该实例内部会有一个指针([prototype]/_proto_),指向构造函数的原型对象。以下图:
上图中 :
Person.prototype指向了原型对象,而Person.prototype.construstor又指回了Person。
注意观察原型对象,除了包含constructor属性以外,还包括后来添加的其它属性,这就是为何每一个实例化后的对象,虽然都不包含属性和方法,可是都包含一个内部属性指向了Person.prototype,能得到原型中的属性和方法。
这个方法叫:Object.getPrototypeOf()
,以下例子:
alert(Object.getPrototypeOf(person1) == Person.prototype);
//truealert(Object.getPrototypeOf(person1).name);
//"Jack"
这个方法能够很方便的取得一个对象的原型
还能够利用这个方法取得原型对象中的name属性的值。
当咱们在建立实例化的对象以后,调用这个实例化的对象的属性时,会前后执行两次搜索。
第一次搜索实例person1有name属性吗?没有进行第二次搜索
第二次搜索person1的原型有name属性吗?有就返回。
所以进行一次思考,若是对实例进行属性重写和方法覆盖以后,访问实例对象的属性和方法会显示哪一个?实例对象的仍是对象原型的?
function Person(){ } Person.prototype.name = "Jack"; Person.prototype.age = 20; Person.prototype.job = "码农"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = "Rose"; alert(person1.name); //Rose alert(person2.name); //Jack
当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。
可是这个属性只会阻止咱们访问原型中的那个属性,而不会修改那个属性
3.使用delete操做符能够删除实例属性,从而从新访问原型中的属性。
function Person(){ } Person.prototype.name = "Jack"; Person.prototype.age = 20; Person.prototype.job = "码农"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = "Rose"; alert(person1.name); //Rose --来自实例 alert(person2.name); //Jack --来自原型 delete person1.name; alert(person1.name); //Jack --来自原型
hasOwnProperty()能够检测一个属性是存在于实例中,仍是原型中,只有在给定属性存在于对象实例中,才会返回true。
person1.hasOwnProperty("name"); //假设name存在于原型,返回false
in操做符会在经过对象可以访问给定属性时返回true,不管该属性是存在于实例中仍是原型中
"name" in person1 //true
因此经过这两个能够封装一个hasPrototypeProperty()
函数肯定属性是否是原型中的属性。
function hasPrototypeProperty(object,name){ return !object.hasOwnProperty(name) && (name in object); }
前面每次添加一个属性和方法都要写一次Person.prototype,为了简即可以直接这样
function Person(){ } Person.prototype = { name:"Jack", age:20, job:"码农", sayName:function(){ alert("this.name"); } };
上述代码直接将Person.prototype设置为等于一个以对象字面量形式建立的新对象
上述这么作时:constructor属性就再也不指向Person了。
本质上彻底重写了默认的prototype对象,所以constructor属性也就变成了新对象的constructor属性(指向Object构造函数)。
所以若是constructor值很重要,能够在Person.prototype
中设置回适当的值:
如上例中能够添加:constructor:Person,
咱们对原型对象所作的任何修改都会当即从实例上反映出来-即便先建立实例对象后修改原型也如此
var friend = new Person(); Person.prototype.sayHi = function(){ alert("Hi"); }; friend.sayHi(); //"Hi"
尽管能够随时为原型添加属性和方法,而且修改能当即在实例对象中体现出来,可是若是重写整个原型对象,就不同了。看下面例子:
function Person(){ } var friend = new Person(); Person.prototype = { constructor:Person, name:"Jack", age:20, sayName:function(){ alert(this.name); } }; friend.sayName(); //error
上述代码先建立了一个Person实例,而后又重写了其原型对象,在调用friend.sayName()
时发生错误。
由于friend指向的原型中不包含以该名字命名的属性。关系以下图:
省略了为构造函数初始化参数这一环节,结果是全部实例都取得相同的属性,但问题不大,能够为实例对象重写属性来解决。
2.可是,对于包含引用类型值的属性
来讲,问题就比较突出了,由于引用类型中,属性名只是一个指针,在实例中重写该属性并无做用。指针始终指向原来的。
以下例子:
function Person(){} Person.prototype = { constructor:Person, name:"Jack", job:"码农", friends:["路人甲","路人乙","路人丙"], }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("路人丁"); alert(person1.friends); //["路人甲","路人乙","路人丙","路人丁"] alert(person2.friends); //["路人甲","路人乙","路人丙","路人丁"] alert(person1.friends === person2.friends); //true
上面这个,假如每一个实例对象的引用值属性不同,则没法修改。
构造函数模式用于定义实例属性
原型模式用于定义方法和共享的属性
以下代码:
function Person(name,age,job){ this.name = name; this.job = job; this.age = age; this.friends = ["路人甲","路人乙"]; } Person.prototype = { constructor:Person, sayName: function(){ alert(this.name); } } var person1 = new Person("Jack", 20, "码农"); var person2 = new Person("Rose", 20, "程序媛"); person1.friends.push("路人丁"); alert(person1.friends); //["路人甲","路人乙","路人丁"] alert(person2.friends); //["路人甲","路人乙"] alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
该模式基本思想是建立一个函数,该函数做用仅仅是封装建立对象的代码,而后返回新建立的对象。
function Person(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var friend = new Person("Jack", 16, "码农"); friend.sayName(); //Jack
构造函数在不返回值的状况下,默认会返回新对象实例。
经过在构造函数末尾添加一个return语句,能够重写调用构造函数时返回的值。
这个方法的用处是:能够建立一个额外方法的特殊的数组(由于原生对象Array的构造函数不能直接修改)
function SpecialArray(){ //建立数组 var values = new Array(); //添加值 values.push.apply(values,arguments); //添加方法 values.toPipedString = function(){ return this.join("|"); }; //返回数组 return values; } var colors = new SpecialArray("black","red","blue"); alert(colors.toPipedString());
原本想接着写继承的,发现实在太多了,分红两篇吧。