属性类型(P139)
JS中有两种属性:数据属性和访问器属性;
数据属性(P139)
数据属性有4个描述其行为的特性:
[[Configurable]]:能否通过delete删除属性从而重新定义属性;
[[Enumerable]]:能否通过for-in循环返回属性;
[[Writable]]:能否修改属性的值;
[[Value]]:包含这个属性的数据值;修改属性的特性:
修改属性的特性,需要使用Object.defineProperty()方法,这个方法接收三个参数:属性所在的对象、属性的名字和一个描述符对象。例如:varperson{}; Object.defineProperty(person,"name",{ writable:false;//不能修改属性的值 value:"Nicholas";//设置属性的值 }); alert(person.name);//"Nicholas" person.name="Greg"; alert(person.name);//"Nicholas"
一旦把属性定义为不可配置的(configurable:false//不能通过delete删除属性),就不能再把它变回可配置了,并且再调用Object.defineProperty()方法修改除writable之外的特性,都会导致错误(即在把configurable特性设置为false之后就会有限制了)
var person={}; Object.defineProperty(person,"name",{ configurable:false; value:"Nicholas"; }); //抛出错误 Object.defineProperty(person,"name",{ configurable:true; value:"Nicholas"; });
访问器属性(P141)
数据属性有4个描述其行为的特性:
[[Configurable]]:能否通过delete删除属性从而重新定义属性;
[[Enumerable]]:能否通过for-in循环返回属性;
[[Get]]:在读取属性时调用的函数;
[[Set]]:在写入属性时调用的函数;
访问器属性可以让两个属性建立关联,通过修改一个属性来改变另一个属性访问器属性不能直接定义,必须使用Object.defineProperty()来定义,如下:
var book={ _year:2004; edtion:1; } Object.defineProperty(book,"year",{ get:function(){ return this._year; }, set:function(newValue){ if(newValue>2004){ this._year=newValue; this.edition+=newValue-2004; } } }) book.year=2005; alert(book.edtion); (这是访问器属性的常见方式,即设置一个属性的值会导致其他属性也发生变化)
定义多个属性(P142)
使用Object.defineProperties()方法,一次性同时定义多个数据属性和访问器属性:
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; } } } })
读取属性的特性(读取上文创建的对象和属性)(P143)
var descriptor=Object.getOwnPropertyDescriptor(book,"_year"); alert(descriptor.value);//2004 alert(descriptor.configurable);//false alert(typeof descriptor.get);//"underfined" var descriptor=Object.getOwnPropertyDescriptor(book,"year"); alert(descriptor.enumerable);//false alert(typeof descriptor.get);//"function" (这里面有些结果有点搞不懂)
创建对象
工厂模式(144)
//用函数来封装以特定接口创建对象的细节 function createPerson(name,age,job){ var 0=new Obeject(); o.name=name; o.age=age; o.job=job; o.sayName=function(){ alert(this.name); }; return o; } var person1=createPerson("Nicholas",29,"Software Enginer"); var person2=createPerson("Greg",27,"Docter");
构造函数模式(144)
//实例 function Person(name,age,job){ this.age=namel this.age=age; this.job=job; this.sayName=function(){ alert(this.name); }; } var person1=new Person("Nicholas",29,"Software Enginer"); var person2=new Person("Greg",27,"Docter"); //创建的person实例,都有一个constructor(构造函数)属性,指向创建它们的函数person alert(person1.constructor==Person); //true alert(person2.constructor==Person); //true //创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,这也是构造函数模式胜过工厂模式的地方 alert(prson1 instanceof Object); //true alert(prson1 instanceof Person); //true //构造函数与作用域 #当构造函数使用 var person=new Person("Nicholas",29,"Software Enginer"); person.sayName();//"Nicholas" #作为普通函数调用 Person("Greg",27,"Doctor");//添加到window window.sayName();//"Greg" #在另一个对象的作用域中调用 var o=new Object(); Person.call(o,"Kristen",25,"Nurse"); o.sayName();//"Kristen" //构造函数的问题 每个方法都要在实例上重新创建一遍,如: alert(person1.sayName==person2,sayName); //false 解决方案(效果不好): function Person(name,age,job){ this.age=namel this.age=age; this.job=job; this.sayName=sayName; } function sayName(){ alert(this.name); } var person1=new Person("Nicholas",29,"Software Enginer"); var person2=new Person("Greg",27,"Docter"); 解决方案的没有封装性,这些问题可以使用原型模式来解决
原型模式(147)
//每个函数都有一个prototype(原型属性),代表调用构造函数而创建的对象实例的原型对象,这个原型对象可以让所有通过构造函数创建的对象实例共享原型对象的属性和方法 function Person(){ } Person.prototype.name="Nicholas"; Person.prototype.age=29; Person.prototype.job="Software Enginer"; Person.prototype.sayName=function(){ alert(this.name); }; var person1=new Person(); var person2=new Person(); alert(person1.sayName==person2,sayName); //true //对象、对象的原型以及通过构造函数生成的实例之间的关系 对象--person 对象的原型--person.prototype 实例--person1 某一个属性--attr person.prototype.constructor==person person1.attr 如果实例本身有属性值,则返回属性值,没有的话则返回实例对应的原型的属性值 如果实例本身设置了属性值,其属性值不会改变原型对应的属性值 //原型与in操作符 在单独使用in操作符时,in操作符会在通过对象能够访问给定属性时返回true,无论改属性存在于实例还是原型中 同时使用hasOwnProperty()方法和in操作符可以确定该属性到底是存在于对象中,还是原型中: function hasPrototypeProperty(object,name){ return !object.hasOwnProperty(name)&&(name in object); } 使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性,其中即包括存在于实例中的属性,也包括存在于原型中的属性 //更简单的原型语法 function person(){ } person.prototype={ name:"Nicholas"; age:29; job:"software Engineer", sayName:function(){ alert(this.name); } }; 完全重写了默认的prototype对象,因此constructor属性不再指向Person函数(指向Object构造函数) var friend=new person(); alert(friend instanceof Object); //true alert(friend instanceof person); //true alert(friend.constructor==person); //false alert(friend.constructor==Object); //false #constructor的值可以手动设置为指定的函数 function person(){ } person.prototype={ //在这里面直接将constructor的值手动的设置为指定的函数,会使constructor属性变为可以枚举的 name:"Nicholas"; age:29; job:"software Engineer", sayName:function(){ alert(this.name); } }; Object.defineProperty(person.prototype,"constructor",{ //重设构造函数,只适用于ECMAScript5兼容的浏览器 enumerable:false, value:person; }); //原型的动态性 #对原型对象所做的任何修改都能立即放映到实例上去,即使修改原型的过程在创建实例之后(但需要在调用函数之前) function Person(){ } var friend=new Person() Person.prototype.sayHi=function(){ alert("hi"); }; friend.sayHi(); #修改原型的属性和方法可以随时在实例上反映出来,但是重写整个原型对象,就切断了构造函数与最初原型之间的联系了(实例的[[Prototype]]还是指向最初的原型,没有指向重写的原型,有两个原型) function person(){ } var friend=new Person() person.prototype={ name:"Nicholas"; age:29; job:"software Engineer", sayName:function(){ alert(this.name); } }; friend.sayName(); //error //原生对象的原型 #所有原生的引用类型,都是在其构造函数的原型上定义的方法,所以通过原生对象的原型,不但可以取得所有默认方法的引用,还可以修改原生对象的原型,随时添加方法: String.prototype.startsWith=function(text){ return this.indexof(text)==0; }; var msg="Hello world!"; alert(msg.startsWith("hello")); //原型对象的问题 #所有实例在默认情况下都取得相同的属性值 #原型中很多属性是被很多实例共享的,这种共享对于包含引用类型的属性来说,问题比较突出: function Person(){ } person.prototype={ constructor:Person, name:"Nicholas"; age:29; job:"software Engineer", friends:["Shelby","Court"], sayName:function(){ alert(this.name); } }; var person1=new Person(); var person2=new Person(); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court,Van" alert(person1.friends===person2.friends); //true
组合使用构造函数模式和原型模式(159)
创建自定义类型的最常见方式,就是组合使用构造函数模式和原型模式,构造函数(支持传参)模式用于定义实例属性,原型模式用于定义方法和共享的属性。这样可以使每个实例都有自己的一份实例属性的副本,同时又共享着对方法的引用,最大限度的节省了内存 function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.friends=["Shelby","Court"]; } person.prototype={ constructor:Person, sayName:function(){ alert(this.name); } }; var person1=new Person("Nicholas",29,"Software Engineer"); var person2=new Person("Greg",27,"Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court" alert(person1.friends===person2.friends); //false alert(person1.sayName()===person2.sayName()); //true //这种构造函数与原型混成的模式,是认同度最高的一种创建自定义类型的方法
动态原型模式159
function Person(name,age,job){ //属性 this.name=name; this.age=age; this.job=job; //方法(实例中不存在sayName方法时,执行的代码) if(typeof this.sayName!="function"){ Person.prototype.sayName=function(){ alert(this.name); }; } } var friend=new Person("Nicholas",29,"Software Engineer"); friend.sayName();
寄生构造函数模式(与工厂模式一样啊)
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("Nicholas",29,"Software Engineer"); friend.sayName(); //"Nicholas"
稳妥构造函数模式161
function Person(name,age,job){ //创建要返回的对象 var o=new Object(); //可以在这里定义私有变量和函数 //添加方法 o.sayName=function(){ alert(name); }; //返回对象 return o; } //新创建对象的实例方法不引用this; //不适用new操作符调用构造函数; //除了sayName()方法,没有其他方法可以访问name的值 var friend=Person("Nicholas",29,"Software Engineer"); friend.sayName(); //"Nicholas"
继承162
JS只支持实现继承,而且实现的方法主要是依靠原型链来实现的
原型链162
利用原型让一个引用类型继承另一个引用类型的属性和方法 function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property; }; function (){ this.subproperty=false; } SubType.prototype=new SuperType();//继承了SuperType SubType.prototype.getSubValue=function(){ return this.subproperty; }; var instance=new SubType(); alert(instance.getSuperValue()); //true //默认的原型 所有默认的原型都是Obeject的实例,这也是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因 //确定原型和实例的关系 alert(instance instanceof Object); //true alert(instance instanceof SuperType); //true alert(instance instanceof SubType); //true 由于原型链的关系,instance既是Object类型,同时也是SuperType和SubType类型 alert(Object.prototype.isPrototypeOf(instance)); //true alert(SuperType.prototype.isPrototypeOf(instance)); //true alert(SubType.prototype.isPrototypeOf(instance)); //true //谨慎的定义方法 function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property; }; function (){ this.subproperty=false; } SubType.prototype=new SuperType();//继承了SuperType SubType.prototype={ //使用字面量添加新方法,会导致上一行代码无效 getSubValue:function(){ return this.subproperty; }, someOtherMethod:function(){ return false; } }; var instance=new SubType(); alert(instance.getSuperValue()); //error 通过原型链实现继承时,不能使用对象字面量创建原型方法,那样会重写原型链 //原型链的问题 包含引用类型值的原型属性会被所有实例(原型链的下一级)共享,所以应该在构造函数中,而非原型对象中定义属性
借用构造函数167
function SuperType(){ this.colors=["red","blue","green"]; } function SubType(){ SuperType.call(this); //继承了SuperType } var instance1=new SubType(); var instance2=new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" alert(instance2.colors); //"red,blue,green" //传递参数 function SuperType(name){ this.name=name; } function SubType(){ SuperType.call(this,"Nicholas"); //继承了SuperType,同时还传递了参数 this.age=29; //实例属性 } var instance=new SubType(); alert(instance.name); //"Nicholas" alert(instance.age); //29 //借用构造函数的问题 方法都在构造函数中定义,函数复用无从谈起
组合继承(伪类经典继承)168
function SuperType(name){ this.name=name; this.colors=["red","blue","green"]; } SuperType.prototype.sayName=function(){ alert(this.name); }; function SubType(name,age){ SuperType.call(this,name); //继承属性 this.age=age; } SubType.prototype=new SuperType(); //继承方法 SubType.prototype.sayAge=function(){ alert(this.age); }; var instance1=new SubType("Nicholas",29); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Nicholas" instance1.sayAge(); //29 var instance2=new SubType("Greg",27); alert(instance1.colors); //"red,blue,green" instance2.sayName(); //"Greg" instance2.sayAge(); //27 //组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,是JS中最常用的继承模式
原型式继承169
function object(o){ function F(){} F.prototype=o; return new F(); } 例子: var person={ name:"Nicholas", friends:["Shelby","Court","Van"] }; var anotherPerson=object(person); //只有一个参数使用Object.create()效果一样 anotherPerson.name="Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson=Object.create(person); yetAnotherPerson.name="Linda"; yetAnotherPerson.friends.push("Barbie"); alert(person.frienss); //"Shelby,Court,Van,Rob,Linda" //Object.create() var person={ name:"Nicholas", friends:["Shelby","Court","Van"] }; var anotherPerson=Object.create(person,{ name:{ value:"Greg" } }); alert(anotherPerson.name); //"Greg" //代码比较精简,但包括引用类型的属性始终都会共享相应的值,就像使用原型模式一样
寄生式继承
寄生式继承模式: function createAnother(original){ var clone=object(original); //通过调用函数创建一个新对象 clone.sayHi=function(){ //以某种方式来增强这个对象 alert("hi"); }; return clone; //返回这个对象 } 使用createAnother()函数: var person={ name:"Nicholas", friends:["Shelby","Court","Van"] }; var anotherPerson=createAnother(person); anotherPerson.sayHi(); //"hi" //新对象不仅具有person的所有属性和方法,而且还设有自己的sayHi()方法
寄生组合式继承
组合继承无论在什么情况下都会调用两次超类型构造函数 寄生组合式继承的基本模式: function inheritPrototupe(subType,superType){ var prototype=object(superType.prototype); //创建对象(创建超类型原型的一个副本) prototype.constructor=subType; //增强对象(为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性) subType.prototype=prototype; //指定对象(将新创建的对象(即副本)复制给子类型的原型) } //应用实例: function SuperType(name){ this.name=name; this.colors=["red","blue","green"]; } SuperType.prototype.sayName=function(){ alert(this.name); }; function SubType(name,age){ SuperType.call(this,name); this.age=age; } inheritPrototupe(subType,superType); SubType.prototype.sayAge=function(){ alert(this.age); };