每个对象都是基于引用类型创建.一般创建对象的方式如下:
var person = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function(){ alert(this.name); } };
数据属性:包含一个数据值的位置,在此位置上可以读取和写入值.它具有以下四个特性:
[[Configurable]]:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性.
[[Enumerable]]:表示能否通过for-in循环返回属性.
[[Writable]]:表示能否修改属性的值.
[[Value]]:包含这个属性的值.
而我们可以通过Object.defineProperty()方法来修改属性默认的特性.它接收三个参数:属性所在的对象,属性的名字和一个描述符对象.而描述符对象必须是configurable,enumerable,writable和value.
var person = {}; Object.defineProperty(person, "name", { writable : false, //说明不可写 value : "Nicholas" }); print(person.name); //输出:Nicholas person.name = "voler"; //不可更改 print(person.name); //输出:Nicholas
特性 Configurable很特殊,一旦修改为false,则无法再次变为可配置(即无法再次设定为true了):
<html><head><meta charset="utf-8" /> <script type="text/javascript"> function print(str) { document.write(str + "<br />"); } var person = {}; Object.defineProperty(person, "name", { writable : true, //说明可写 configurable : false, value : "Nicholas" }); //writable属性 print(person.name); //输出:Nicholas person.name = "voler"; //可更改 print(person.name); //输出:voler //configurable属性 delete person.name; //不可删除 print(person.name); //输出:voler try { Object.defineProperty(person, "name", { configurable : true, value : "Nicholas" }); } catch(err) { print(err); //输出:TypeError: Cannot redefine property: name } </script></head></html>
访问器属性:不包含数据值,但是通过get/set函数来操作数据,通过设定属性名来存储数据.它具有[[Configurable]],[[Enumerable]],[[Get]]:读取数据,[[Set]]:写入数据.设置一个属性导致其他属性发生变化.
var book = { _year : 2004, edition : 1 }; //通过设定year属性名来操作对象 Object.defineProperty(book, "year",{ get : function() { print("3"); return this._year; }, set : function(newValue) { print("1"); if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }); //对属性year的赋值,实际上会调用set函数 book.year = 2005; print("2"); //而读取year属性,实际上会调用get函数--通过set/get函数,可以达到数据的私有化 print(book.year);浏览器显示如下:
通过Object.defineProperties()方法达到.
<script type="text/javascript"> function print(str) { document.write(str + "<br />"); } var book = {}; Object.defineProperties(book, { _year: { value: 2004, writable: true //这里必须设置writable属性:否则默认无法进行修改,在定义多个属性的情况下 }, edition: { value: 1 }, year: { get: function() { print("get"); return this._year; }, set: function(newValue) { print("set"); if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } } }); book.year = 2006; print(book.year); //2006 </script>
通过Object.getOwnPropertyDescriptor()方法达到.
<html><head><meta charset="utf-8" /> <script type="text/javascript"> function print(str) { document.write(str + "<br />"); } var book = {}; Object.defineProperties(book, { _year : { value : 2004 }, edition : { value : 1 }, year : { get : function() { return this._year; }, set : function(newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } } }); debugger; var descriptor = Object.getOwnPropertyDescriptor(book, "_year"); print(descriptor.value); //2004 print(descriptor.configurable); //false print(typeof descriptor.get); //undefined //对于访问器属性,是没有value的,所以不能通过descriptor.get来得到值 var descriptor = Object.getOwnPropertyDescriptor(book, "year"); print(descriptor.value); //undefined print(descriptor.enumerable); //false print(typeof descriptor.get); //function </script></head></html>
用函数来封装以特定接口创建对象的细节:
function createPerson(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function() { print(this.name); }; return o; } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor");
创建自定义的构造函数,从而定义自定义对象类型的属性和方法:
//这里this对应new出来的对象 function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function() { print(this.name); } } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");与之前的工厂模式不同之处在于:
1. 没有显式的创建对象.
2. 直接将属性和方法赋给了this对象.
3. 没有return语句.
而创建Person的新实例,需要使用new操作符.当调用new的时候,会经历以下四个步骤:
1. 创建一个新对象.
2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
3. 执行构造函数中的代码(为这个新对象添加属性)
4. 返回新对象.
而创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,这正是构造函数胜过工厂模式的地方.
print(person1 instanceof Object); //true print(person2 instanceof Person); //true print(person1 instanceof Object); //true print(person2 instanceof Person); //true
构造函数和一般函数没什么区别.函数只要用new操作符来调用,就是构造函数,否则就是一般函数:
//这里this对应new出来的对象 function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function() { print(this.name); } } //this--->person var person = new Person("Nicholas", 29, "Software Engineer"); person.sayName(); //Nicholas //this--->window Person("Greg", 27, "Doctor"); window.sayName(); //Greg sayName(); //Greg var o = new Object(); Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //Kristen
每个方法都要在每个实例上重新创建一遍.
我们创建的每个函数都有一个prototype(原型属性),这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法.
function Person(){} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Enginner"; Person.prototype.sayName = function(){ print(this.name); }; var person1 = new Person(); person1.sayName(); //Nicholas var person2 = new Person(); person2.sayName(); //Nicholas
这里在firebug上有一个很有启发性的调试:
无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象.在默认情况下,所有原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针.
person1.constructor.prototype === Person.prototype person2.constructor === person1.constructor
而isPrototypeOf()和getPrototypeOf()方法用来确定原型对象和实例之间的关系:
print(Person.prototype.isPrototypeOf(person1)); //true print(Person.prototype.isPrototypeOf(person2)); //true print(Object.getPrototypeOf(person1) == Person.prototype); //true print(Object.getPrototypeOf(person1).name); //Nicholas虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值.如果我们向实例中添加一个属性,而该属性与实例原型中的一个属性同名,那我们会屏蔽原属性.我们可以用delete来删除掉添加的属性.
<html><head><meta charset="utf-8" /> <script type="text/javascript"> function print(str) { document.write(str + "<br />"); } function Person(){} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Enginner"; Person.prototype.sayName = function(){ print(this.name); }; var person1 = new Person(); var person2 = new Person(); //屏蔽掉原属性 person1.name = "Greg"; print(person1.name); //Greg print(person2.name); //Nicholas //删除添加的属性 delete person1.name; print(person1.name); //Nicholas </script></head></html>关系如下:
in操作符会在通过对象能够访问给定属性时返回true.
<html><head><meta charset="utf-8" /> <script type="text/javascript"> function print(str) { document.write(str + "<br />"); } //hasOwnProperty只有在实例中才返回true function hasPrototypeProperty(object, name) { return !object.hasOwnProperty(name) && (name in object); } function Person(){} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Enginner"; Person.prototype.sayName = function(){ print(this.name); }; //函数原型中定义name var person = new Person(); print(hasPrototypeProperty(person, "name")); //true //函数实例中定义name person.name = "Greg"; print(hasPrototypeProperty(person, "name")); //false </script></head></html>而我们可以通过Object.keys()方法来枚举实例属性:
function Person(){} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Enginner"; Person.prototype.sayName = function(){ print(this.name); }; var keys = Object.keys(Person.prototype); print(keys); //name,age,job,sayName
function Person(){} Person.prototype = { name: "Nicholas", age: 29, job: "Software Engineer", sayName: function() { print(this.name); } }; var person1 = new Person();这里要注意一点是:constructor属性不再指向Person,而是指向Object.是因为赋值会断开原型链,所以constructor指向新的对象Person.prototype,而此对象为Object类型.
而更具体的解释如下:
<script type="text/javascript"> function print(str) { document.write(str + "<br />"); } function Person(){} Person.prototype = { name: "Nicholas", age: 29, job: "Software Engineer", sayName: function() { print(this.name); } }; var person1 = new Person(); function Func(){} Func.prototype.name = "lcj"; var person2 = new Func(); </script>firebug的调试如下:
这里对Person.prototype = ***;的赋值操作,实际上可以理解为继承:
<script type="text/javascript"> function print(str) { document.write(str + "<br />"); } function Person(){} function tempObject(){}; tempObject.prototype.name = "Nicholas"; tempObject.prototype.age = 29; tempObject.prototype.job = "Software Engineer"; tempObject.prototype.sayName = function() { print(this.name); }; Person.prototype = new tempObject(); var person1 = new Person(); </script>这里,tempObject模拟了所有对象的原始父类:Object对象.firebug调试如下:
实例和原型之间通过prototype指针连接.所以原型的任何修改会时时反应到实例上:
var friend = new Person(); Person.prototype.sayHi = function(){ print("Hi"); }; friend.sayHi(); //输出:Hi但是,如果重写原型对象.由于断开了实例和原型之间的关联,则以下代码会出错:
function Person(){} var friend = new Person(); Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job : "Software Enginner", sayName : function(){ print(this.name); } }; friend.sayName(); //error
共享性:
function Person(){} var friend = new Person(); Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job : "Software Enginner", friend : ["Shelby", "Court"], sayName : function(){ print(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friend.push("Van"); print(person1.friend); //Shelby,Court,Van print(person2.friend); //Shelby,Court,Van
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性.结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用.
<html><head><meta charset="utf-8" /> <script type="text/javascript"> function print(str) { document.write(str + "<br />"); } function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ["Shelly", "Court"]; } Person.prototype = { constructor : Person, sayName : function() { print(this.name); } } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); print(person1.friends); //Shelly,Court,Van print(person2.friends); //Shelly,Court print(person1.sayName == person2.sayName); //true </script></head></html>
动态原型模型把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点.
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; //只有在sayName不存在的情况下,执行sayName的初始化 if (typeof this.sayName != "function") { Person.prototype.sayName = function() { alert(this.name); }; } }
思想为创建一个函数,作用仅仅是封装创建对象的代码,然后返回新创建的对象--除了new之外,和工厂函数没什么区别.
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 person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = Person("Nicholas", 29, "Software Engineer");但是这里有一个疑问是:person1 === person2明显为false,但是它们不同的地方在哪里?
稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象:
function Person(name, age, job) { var o = new Object(); o.sayName = function() { alert(name); }; return o; }这里直接对传入的参数进行操作,所以没有公共属性,也没不引用this对象.
构造函数,原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针.我们让原型对象包含一个指向构造函数的指针,而实例包含一个指向原型对象的内部指针,这就是原型链的继承方式.
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType() { this.subproperty = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function() { return this.subproperty; }; var instance = new SubType(); print(instance.getSuperValue()); //true //这时候,构造函数为SuperType,而非SubType print(instance.constructor); //function SuperType() { this.property = true; }
而完整的继承图应该包含Object:
而我们可以通过instanceof操作符和isPrototypeOf方法来测试原型和实例之间的关系:
//通过instanceof来确定原型和实例的关系 print(instance instanceof Object); //true print(instance instanceof SuperType); //true print(instance instanceof SubType); //true //通过isPrototypeOf来确定原型和实例的关系 print(Object.prototype.isPrototypeOf(instance)); //true print(SuperType.prototype.isPrototypeOf(instance)); //true print(SubType.prototype.isPrototypeOf(instance)); //true我们需要谨慎的定义方法:给原型(这里为继承的超类型SuperType)添加的方法必须在替换原型的语句(SubType.prototype = new SuperType)的后面,因为原型链为:SubType的实例指向SubType.prototype原型,而 SubType.prototype原型包含一个SuperType的实例(new SuperType),而此实例又指向SuperType的原型(SuperType.prototype).所以,为定义超类(SuperType)的实例之前,为其添加方法是错误的:(但是我觉得这里有点莫名其妙!):
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType() { this.subproperty = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function() { return this.subproperty; }; //添加新方法 SubType.prototype.getSubValue = function() { return this.subproperty; }; var instance = new SubType(); //重写超类中的方法 SubType.prototype.getSuperValue = function() { return "hello"; }; var instance = new SubType(); print(instance.getSubValue()); //false print(instance.getSuperValue()); //hello
之前自己对函数有一定的认知误区,目前总结如下:
1. 只有对象才能添加其属性和方法.
2. 函数本身是一个对象,函数名仅仅指向此函数,并不能通过函数名来添加属性和方法(即函数名并不是一个对象)
3. 要给函数添加方法,要么通过具有对象的函数原型(FUNC.prototype),要么对new FUNC()出来的对象进行添加.所以以下代码很有参考作用:
function SuperType() { this.property = true; this.show1 = function() { print("show1 function"); }; } //不能通过SuperType函数名来添加方法 SuperType.show2 = function() { print("show2 function"); }; var superInstance = new SuperType(); superInstance.show3 = function() { print("show3 function"); }; superInstance.show1(); //superInstance.show2(); //-此处报异常,show2非函数 superInstance.show3();
但是,原型链有以下两个问题:1.共享机制(跟模型的原理一致)
function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() { } SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); print(instance1.colors); //red,blue,green,black var instance2 = new SubType(); print(instance2.colors); //red,blue,green,black第二个问题是:不能向超类中传递参数.
在子类型构造函数的内部调用超类型构造函数(但这种方法谈不上复用,因此很少使用)
function SuperType(name) { this.colors = ["red", "blue", "green"]; this.name = name; } function SubType() { SuperType.call(this, "Nicholas"); this.age = 29; } SubType.prototype = new SuperType();//此句没有任何的作用,删除也可以 //不共享原型,每个实例均有自己的原型副本 var instance1 = new SubType(); instance1.colors.push("black"); print(instance1.colors); //red,blue,green,black var instance2 = new SubType(); print(instance2.colors); //red,blue,green print(instance1.name); //Nicholas print(instance1.age); //29
组合继承,也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合在一起.其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承.这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性:
function SuperType(name) { this.colors = ["red", "blue", "green"]; this.name = name; } SuperType.prototype.sayName = function() { print(this.name); }; function SubType(name, age) { //继承属性 SuperType.call(this, name); this.age = age; } //继承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { print(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); print(instance1.colors); //red,blue,green,black instance1.sayName(); //Nicholas instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27); print(instance2.colors); //red,blue,green instance2.sayName(); //Greg instance2.sayAge(); //27
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型:
function object(o) { function F(){} F.prototype = o; return new F(); }
例子如下:
function object(o) { function F(){} F.prototype = o; return new F(); } var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); print(person.friends); //Shelby,Court,Van,Rob,Barbie而ECMAScript5新增Object.create()方法,接收两个参数:一个用作新对象原型的对象和一个为新对象定义额外属性的对象.
var anotherPerson = Object.create(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob");
Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的.以这种方式指定的任何属性都会覆盖原型对象上的同名属性:
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; //通过此方法会重新定义name的属性,所以如果name中不编写value: "true",则调用anotherPerson.name为undefined var anotherPerson = Object.create(person, { name: { value: "true", writable: false } }); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = Object.create(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); print(person.friends); //Shelby,Court,Van,Rob,Barbie print(anotherPerson.name); //true
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象:
function object(o) { function F(){} F.prototype = o; return new F(); } function createAnother(original) { var clone = object(original); clone.sayHi = function() { print("Hi"); }; return clone; } var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi();
function functionName(arg0, arg1, arg2) { //函数体 } var functionName = function(arg0, arg1, arg2) { //函数体 };
经典的但是错误的递归函数(在javascript是错误的):
function factorial(num) { if (num <= 1) { return 1; } else { return num * factorial(num - 1); } } var anotherFactorial = factorial; factorial = null; print(anotherFactorial(4)); //出错!!而我们应该使用arguments.callee来指向正在执行的函数的指针.
function factorial(num) { if (num <= 1) { return 1; } else { return num * arguments.callee(num - 1); } } var anotherFactorial = factorial; factorial = null; print(anotherFactorial(4)); //24而我们通过函数表达式可以优化设计:
var factorial = (function f(num) { if (num <= 1) { return 1; } else { return num * f(num - 1); } }); print(factorial(4)); //24 var anotherFactorial = factorial; factorial = null; print(anotherFactorial(4)); //24
闭包指的是有权访问另一个函数作用域中的变量的函数.创建闭包的常用方式,就是在一个函数内部创建另一个函数:
function createComparisonFunction(propertyName) { return function(object1, object2) { var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } }; } var compare = createComparisonFunction("name"); var result = compare({name : "Nicholas"}, {name : "Greg"}); print(result); //1作用域链如下:
从上图可以看出,闭包的对象并不会被销毁.闭包的对象会一直存在于声明它的实例中(在compare实例),直到手动设置为null.
//创建函数 var compare = createComparisonFunction("name"); //调用函数 var result = compare({name : "Nicholas"}, {name : "Greg"}); //解除对匿名函数的引用 compare = null;
作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值.闭包所保存的是整个变量对象,而不是某个特殊的变量.
function createFunctions() { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = function() { return i; }; } return result; } var result = createFunctions(); for (var i = 0; i < result.length; i++) { print(result[i]()); //输出10 10 10 10 10 10 10 10 10 10 }
而我们可以强制传递参数进行,让函数符合预期的结果:
function createFunctions() { var result = new Array(); for (var i = 0; i < 10; i++) { result[i] = function(num) { return num; }(i); } return result; } var result = createFunctions(); for (var i = 0; i < result.length; i++) { print(result[i]); //输出0 1 2 3 4 5 6 7 8 9 }
而匿名函数的执行环境具有全局性:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function() { return function() { //这里this是window return this.name; }; } }; print(object.getNameFunc()()); //The Window
我们可以修改如下:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function() { //这里this是object var that = this; return function() { return this.name + "---" + that.name; }; } }; print(object.getNameFunc()()); //The Window---My Object
函数具有作用域,并且多次声明一个变量并不起作用:javascript对第一个声明有效,后续声明均视而不见.
function outputNumbers(count) { for (var i = 0; i < count; i++) { ; } var i; //再次声明忽略不计 print(i); } outputNumbers(10); //10
而我们因此可以使用此特性编写私有作用域:
(function(){ //这里是块级作用域 })();
javascript本身并没有私有变量一说.但是由于存在函数作用域,则在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量.私有变量包括函数参数,局部变量和在函数内部定义的其他函数:
function Person(name) { this.getName = function() { return name; }; this.setName = function(value) { name = value; }; } var person = new Person("Nicholas"); print(person.getName()); //Nicholas person.setName("Greg"); print(person.getName()); //Greg我们也可以定义静态私有变量:
(function(){ var name = ""; //未使用var,故为全局变量 Person = function(value){ name = value; }; Person.prototype.getName = function(){ return name; }; Person.prototype.setName = function(value){ name = value; }; })(); var person1 = new Person("Nicholas"); print(person1.getName()); //Nicholas person1.setName("Greg"); print(person1.getName()); //Greg var person2 = new Person("Michael"); print(person1.getName()); //Michael print(person2.getName()); //Michael