属性类型
ECMAScript中有两种属性:数据属性和访问器属性。
数据属性包含一个数据值的位置。数据属性有4个行为描述其行为的特性。
想要修改属性的默认特性,必须使用Object.defineProperty(),接受三个参数,属性所在的对象,属性的名字和一个描述符对象。调用这个方法时,如果不设定,configurable,enumberable,writable特性的默认值是false。
访问器属性不包含数据值,包含一对getter和setter函数。具有四个特性:
定义多个属性Object.defineProperties()
创建对象
工厂模式(创建多个相似对象,但是没解决对象类型识别问题)
创建对象可以通过Object构造函数或者对象字面量形式创建单个对象。但是这两种方式有明显的缺点,通过同一个接口创建很多对象,会产生大量重复的代码,于是我们开始使用工厂函数的一种变体。
function factory (name, age, job) {
var person = new Object()
person.name = name
person.age = age
person.job = job
person.sayName = function () {
alert(this.name)
}
return person
}
var person1 = factory(‘nick’,19,’stduent’)
var person2 = factory(‘lucy’,29,’hr’)
构造函数模式
function Person (name, age, job) {
this. name = name
this.age = age
this.job = job
this.sayName = function (){
alert(this.name)
}
}
var person1 = new Person(‘nick’,19,’stduent’)
var person2 = new Person(‘lucy’,29,’hr’)
构造函数的函数名首字母应该大写,这借鉴了其他OO语言,构造函数本身也是函数,只不过可以用来创建对象而已。
要创建一个Person的实例,必须使用new构造符。以这种方式调用构造函数会经历一下4步。
1.创建一个新对象。
2.将构造函数的作用域赋给新对象(因此this就指向这个对象)
3.执行构造函数中的代码(为这个新对象添加属性)
4.返回新对象
检测创建的对象是不是Object或Person的实例,建议使用instanceof
构造函数与普通函数的区别是调用方法不同。使用new操作符来调用,那它就可以作为构造函数,而任何函数,如果不通过new调用,那就和普通函数没有区别。
//作为构造函数调用,以上一个Person()函数为例
var person3 = new Person(‘anna’,20,’doctor’)
person3.sayName() //‘anna’
//作为普通函数调用
Person(‘john’,25,’teacher’)
window.sayName() //‘john’
//在另一个对象的作用域调用
var person4 = new Object()
Person.call(person4,’allen’,26,’worker’)
person4.sayName() //‘allen’
构造函数缺点:每个方法都要在每个实例上创建一遍。ECMAScript中函数也是对象,每创建一个函数,就实例化一个对象。
原型模式
原型模式,使用原型对象的好处是可以将所有对象实例共享它所包含的属性和方法。不必在构造函数中定义对象的实例信息,而是直接将这些信息添加在原型对象中。
function Person(){
}
Person.prototype.name = 'nick'
Person.prototype.age = '20'
Person.prototype.job = 'teacher'
Person.prototype.sayName = function () {
alert(this.name)
}
var person1 = new Person()
Person.sayName() //'nick'
原型对象:无论何时,只要创建了一个函数,都会生成一个prototype属性,指向函数的原型对象。而所有的原型对象都会有一个constructor属性指向prototype属性所在函数。当调用构造函数创建一个实例后,实例内部会有一个内部属性proto指向构造函数的原型对象。
Person.prototype.constructor = Person
使用isPrototypeOf()检测原型对象和实例之间关系
alert(Person.prototype.isPrototypeOf(person1)) //true
使用Object.getPrototypeOf() 返回双下划线proto的值
alert(Object.getPrototypeOf(person1) == Person.prototype) //true
alert(Object.getPrototypeOf(person1).name) // nick
使用hasOwnProperty() 检测一个属性存在于实例中还是原型中。存在于实例属性返回true,原型中返回false。
虽然可以通过对象实例访问保存在原型中的值,但是不能通过对象实例重写原型中的值。如果我们给实例添加一个属性,而这个属性和原型中的一个属性同名,那我们就在实例中创建该属性,该属性会屏蔽掉原型中的属性。同时通过delete操作符可以删除实例属性。
function Person(){
}
Person.prototype.name = 'nick'
Person.prototype.age = '20'
Person.prototype.job = 'teacher'
Person.prototype.sayName=function (){
alert(this.name)
}
var person1 = new Person()
var person2 =new Person()
person1.name = 'alice'
alert(person1.name) //'alice'
alert(person2.name) //'nick'
alert(person1.hasOwnProperty(name)) //true
alert('name' in person1) //true
delete person1.name
alert(person1.name) //'nick'
alert(person1.hasOwnProperty(name)) //false
alert('name' in person1) //true in操作符表示只要对象能够访问到属性就返回true,不管是在原型还是实例中。
要取得对象上所有可枚举的实例属性,可以使用Object.keys(),接收一个参数,返回可枚举属性的字符串数组。
如果想得到所有实例属性,无论是否可枚举(constructor属性),可以使用Object.getOwnPropertNames()。
使用对象字面量重写整个原型,这时候constructor指向不会再指向Person函数,指向Object构造函数。可以用instantOf()操作符返回正确的结果,但是通过constructor已经无法确定对象的类型。如果constructor的值真的很重要,可以手动设置适当的值。
function Person () {
}
Person.prototype = {
constructor : Person,
name : 'nick',
age : '20',
job : 'teacher',
sayName : function () {
alert (this.name)
}
}
var person1 = new Person()
alert(person1 instanceOf Person) //true
alert(person1 instanceOf Object) //true
alert(person1.constructor == person) //false
alert(person1.constructor == Object) //true
构造函数并不是没有缺点,它省略了为构造函数传递出实话参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。原型模式的最大问题是其共享的本质所导致的。原型中共享对于函数非常合适,对于包含基本值的属性也还说的过去,通过在实例上添加同一属性名,可以隐藏原型中对应的属性。但是对于包含引用类型的值的属性来说,问题很突出。
function Person () {
}
Person.prototype = {
constructor : Person,
name : 'nick',
age : '20',
job : 'teacher',
friends: [‘lily’,’john’],
sayName : function () {
alert (this.name)
}
}
var person1 = new Person()
var person2 = new Person()
person1.friends.push(‘mike’)
alert(person1.friends) // ['lily','john','mike']
alert(person2.friends) // ['lily','john','mike']
alert(person1.friends == person2.friends) //true
组合使用构造函数和原型模式
将实例属性都定义在构造函数中,共享的属性constructor和方法定义在原型中。
function Person (name, age,job) {
this.name = name
this.age = age
this.job = job
this.friends = ['lily','john']
}
Person.prototype = {
constructor : Person,
sayName : function () {
alert (this.name)
}
}
var person1 = new Person('nick','20','teacher')
var person2 = new Person('lucy','18','student')
person1.friends.push('mike')
alert(person1.friends) // [‘lily’,’john’,’mike’]
alert(person2.friends) // [‘lily’,’john’]
alert(person1.friends == person2.friends) //false
alert(person1.sayName == person2.sayName) //true
动态原型模式
将所有的信息封装在构造函数里,而在构造函数中初始化原型(仅在必要条件下),保持了构造函数和原型的优点。
function Person (name,age,job) {
this.name = name
this.age = age
this.job = job
if (typeof this.sayName != 'function') {
Person.prototype.sayName = function () {
alert (this.name)
}
}
}
var friend = new Person('nick','20','techer')
friend.sayName() //nick
这里只有在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 person1 = new Person('nick','20','teacher')
person1.sayName() //'nick'
稳妥构造函数(不使用this,new)
function Person (name,age,job) {
var o = new Object()
o.sayName = function (){
alert(name)
}
return o
}
var person1 = Person('nick','20','teacher')
person1.sayName() //'nick'
继承
ECMAScript中继承主要通过原型链继承。
借用构造函数,在子构造函数内部调用超类型构造函数,使用这种方法无法实现函数复用
function a () {
this.color = ['green','red','blue']
}
function b () {
a.call(this) //b继承了a
}
var Color1 = new b()
Color1.color.push('black')
alert(Color1.color) //['green','red','blue’,’black’]
var Color2 = new b()
alert(Color2.color) //['green','red','blue']
组合继承
function a (name) {
this.name = name
this.color = ['green','red','blue']
}
a.prototype.say = function () {
alert(this.name)
}
function b(name,age) {
a.call(this,name)
this.age = age
}
b.prototype = new a()
b.prototype.constructor = b
b.prototype.sayAge = function (){
alert(this.age)
}
var b1 = new b('nick','10','student')
b1.color.push('black')
alert(b1.color) //['green','red','blue','black']
b1.say() // 'nick'
b1.sayAge() //'10'
var b2 = new b('Anna','20','teacher')
alert(b2.color) //['green','red','blue']
b2.say()//'Anna'
b2.sayAge() //'20'
原型式继承/寄生式继承/寄生组合式继承