js笔记(4) --面对对象的程序设计

理解对象

ECMA-262将对象定义为无序属性的集合,其属性可以包含基本值、对象或者函数。

属性类型

ECMAScript通过特性描述了属性的各种特征,不能直接访问特性,规范中通过[[]]描述特性。
ECMAScript中有两种属性,数据属性和访问器属性。

  1. 数据属性
    数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有4个描述其行为的特性。
特性说明
[[Configurable]]是否可以通过delete删除属性;能否修改属性的特性;能否把属性修改为访问其属性;默认值为true
[[Enumerable]]能否通过for-in循环返回属性,默认为true
[[Writable]]能否修改属性,默认为true
[[Value]]包含这个属性的数据值,默认为undefined
  • Object.defineProperty(属性所在对象,属性的名字,描述符对象),修改属性默认的特性
//创建一个只读的name属性
var person={}
Object.defineProperty(person,'name',{
	wriabled:false,
	value:'marry'
})
person.name  //marry
person.name='mike' //在严格模式下会报错
person.name  //marry

注意:一旦通过Object.defineProperty()定义了configurable为false,就不可以逆转了。

  1. 访问器属性
    访问器属性不包含数据值,包含getter、setter函数,在读取访问器属性时,调用getter函数,返回有效的值,在写入访问器属性时,会调用setter函数并传入新值。
特性说明
[[Configurable]]是否可以通过delete删除属性;能否修改属性的特性;能否把属性修改为访问其属性;默认值为true
[[Enumerable]]能否通过for-in循环返回属性,默认为true
[[Get]]在读取属性时调用的函数,默认为undefined
[[Set]]在写入属性时调用的函数,默认为undefined
var book={
	_year:2004,
	edition: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
console.log(book.edition) //2

下划线用于表示只能通过对象方法访问的属性。只指定getter意味着属性不能写,只指定setter意味着属性不能读。

定义多个属性

Object.defineProperty()用于定义单个属性,Object.defineProperties()可以定义多个属性。

  • Object.defineProperties(要添加和修改其属性的对象,对象的属性与第一个参数对象中要添加或修改的属性一一对应)
var person={}
Object.defineProperties(person,{
	name:{
		value:'marry'
	},
	_age:{
		value:23
	},
	age:{
		get:function(){
				return this._age
		}
	}
	
})
person.name  //"marry"
person.age  //23

读取属性的特性

  • Object.getOwnPropertyDescriptor(属性所在的对象,要读取其描述符的属性名称),返回值是一个对象(若为访问器属性,这个对象的属性有configurable,enumerable,get,set,若为数据属性,这个对象的属性有configurable,enumerable,writable,value)
var person={}
Object.defineProperties(person,{
	name:{
		value:'marry'
	},
	_age:{
		value:23
	},
	age:{
		get:function(){
				return this._age
		}
	}
	
})
var desc=Object.getOwnPropertyDescriptor(person,"_age")
desc.value //23
desc.configurale //false

创建对象

使用构造函数或对象字面量创建对象,会产生许多重复的代码。可以采用以下几种模式避免重复代码。

工厂模式

工厂模式抽象了创建具体对象的过程,用函数封装了创建对象的细节。在函数内部创建对象,然后将该对象以返回值的形式传递出去。


function createPerson(name,age){
	var o=new Object()
	o.name=name
	o.age=age
	o.sayName=function(){
		console.log(this.name)
	}
	return o
}
var p1=createPerson('ma',12)
var p2=createPerson('ss',23)
p1.sayName() //ma
p2.sayName()//ss
优点缺点
避免创建相同对象的重复代码无法确定对象的类型

构造函数模式

通过构造函数来创建对象,构造函数的语法,一般构造函数的首字母需要大写,通过this将属性和方法赋给对象,不需要return语句。

function Person(name,age){
	this.name=name
	this.age=age
	this.sayName=function(){
		console.log(this.name)
	}
}
var p1=new Person('ma',12)
var p2=new Person('ss',23)

通过new创建实例,一般经历以下几个步骤:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象;
  3. 执行构造函数中的代码;
  4. 返回新对象。

通过构造函数创建的实例都有constructor属性,该属性指向创建实例的构造函数。可以通过该属性判断对象的类型。(判断类型还可以用instanceof操作符)

p1.constructor //Person(name,age){this.name=name;this.age=age;this.sayName=function(){console.log(this.name)}}
优点缺点
解决了工厂模式无法确定对象类型的缺点若将方法定义在构造函数中,每创建一个实例都会重新创建一次方法;若将方法定义在构造函数外,该方法在全局作用域下失去了封装性

原型模式

每个函数都有prototype属性,这个属性是一个指针,指向一个包含所有实例共享的属性和方法的对象(原型对象)。

  1. 原型对象
    每个函数都有prototype属性指向原型对象;原型对象有constructor属性指向prototype属性所在的函数;每个实例可以通过__proto__属性访问原型对象。
function Person(){}
Person.prototype.name='marry'
Person.prototype.age=23
Person.prototype.sayName=function(){
	console.log(this.name)
}
var person=new Person()
person.sayName()  //marry
Person.prototype===person.__proto__ //true
Person.prototype.constructor //Person(){}

  1. 对象与原型对象
  • 判断对象和原型对象之间的关系
    • isPrototypeOf(),判断对象的原型
    • Object.getPrototypeOf(),返回实例的原型对象的值
Person.prototype.isPrototypeOf(person)  //true
Object.getPrototypeOf(person)===Person.prototype //true
  • 实例与原型中的属性
    • 访问实例属性时,若实例中不存在该属性,会去原型对象中查找。
    • 在实例中定义的属性和方法,若在原型中存在,该实例中的属性和方法会覆盖原型中的属性和方法。
    • hasOwnProperty()和in操作符,检测属性是在原型中还是实例中
      • hasOwnProperty(),当属性存在于对象实例中时,返回true
      • in操作符,当属性存在于对象实例或者原型中时,返回true
person.hasOwnProperty('name')  //false
"name" in person  //true
  1. 原型对象语法
    可以通过Person.prototype.的方式创建原型,还可以通过创建字面量的方式创建原型。
function Person(){}
Person.prototype={
	name:'marry',
	age:23,
	sayName(){
		console.log(this.name)
	}
}
var p=new Person()
p.sayName()  //marry
Person.prototype.constructor //Object(){}
p.constructor  //Object(){}

通过对象字面量的方式创建原型对象相当于重新创建了对象,此时原型对象的constructor属性不再指向Person。

function Person(){}
Person.prototype={
	constructor:Person,
	name:'marry',
	age:23,
	sayName(){
		console.log(this.name)
	}
}
var p=new Person()
p.sayName()  //marry
Person.prototype.constructor //Person(){}
p.constructor  //Person(){}

通过上述方式添加constructor属性会将constructor属性的可枚举属性变为true。可以通过Object.defineProperty()设置constructor属性。

Object.defineProperty(Person.prototype,"constructor",{
		enumerable:true,
		value:Person
})
Person.prototype.constructor //Person(){}
p.constructor  //Person(){}
优点缺点
所有实例可以共享属性和方法实例共享引用类型属性会带来问题,一个实例改变了该属性,另一个实例中属性也会随之改变

组合使用构造函数模式和原型模式

构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。

function Person(name,age){
	this.name=name
	this.age=age
}
Person.prototype={
	constructor:Person,
	sayName:function(){
		console.log(this.name)
	}
}
var p1=new Person('mike',23)
var p2.new Person('jack',34)
p1.sayName()  //mike
p2.sayName() //jack

优点:每个实例都会有一份实例属性的副本,又共享的方法,节省了内存。

动态原型模式

将所有代码封装在构造函数中,通过在构造函数初始化原型。

function Person(name,age){
	this.name=name
	this.age=age
	if(typeof this.sayName!="function"){
		Person.prototype.sayName=function(){
			console.log(this.name)
		}
	}
}
var p=new Person('mike',23)
p.sayName()  //mike

优点:仅在有必要的情况下初始化原型,同时具有组合构造函数和原型的优点。

寄生构造函数模式

创建一个函数,该函数封装了创建对象的代码,然后返回新创建的对象。类似于工厂模式,不同的是通过构造函数的方式创建对象。以该方式创建对象无法确定对象类型。


function Person(name,age){
	var o=new Object()
	o.name=name
	o.age=age
	o.sayName=function(){
		console.log(this.name)
	}
	return o
}
var p1=new Person('ma',12)
p1.sayName()  //ma
p1.constructor  //Object(){}

稳妥构造函数模式

稳妥对象指的是没有公共属性,而且其方法也不引用this的对象。(在一些安全的环境中使用)创建方式类似于寄生构造函数模式,但有两种不同,一是新创建的对象的实例方法不引用this,二是不使用new操作符调用构造函数。

function Person(name,age){
	var o=new Object()
	o.name=name
	o.age=age
	o.sayName=function(){
		console.log(this.name)
	}
	return o
}
var p1= Person('ma',12)
p1.sayName()  //ma
p1.constructor  //Object(){}

继承

在面对对象语言中,一般继承分为接口继承和实现继承,接口继承只继承方法签名,实现继承则继承实际的方法。ECMAScript只支持实现继承。

原型链

  1. 创建原型链
    基本思想:一个引用类型继承另一个引用类型的属性和方法。
    方法:子类的原型对象等于父类对象的实例。
function Sub(){
	this.property=true
}
function Super(){
	this.property=false
}
Super.prototype.getProperty=function(){
	return this.property
}
Sub.prototype=new Super()  //继承了Super
var s1=new Sub()
s1.getProperty()  //true

注意:

  • 为原型添加方法时需要在继承语句之后,继承语句相当于重写了原型对象。
  • 不能使用对象字面量的方式创建原型对象,会重写原型
  1. 原型链的问题
  • 当子类原型对象继承父类实例时,父类实例中的属性变成了子类中共享的属性,对于引用类型的数据来说存在问题,当一个子类实例对属性进行了更改,其他实例的属性值也会改变。
  • 在创建子类型的实例时,不能向超类型的构造函数中传递参数。

借用构造函数

  1. 基本思想
    在子类型的构造函数的内部调用超类型的构造函数。通过apply或call在子类型中调用构造函数。
function Super(){
	this.property=true
}
function Sub(){
	Super.call(this)
}
var sub=new Sub()
sub.property  //true
  1. 优缺点
    优点:可以向超类型构造函数中传递参数;
    缺点:方法都在构造函数中定义,复用性不好

组合继承

基本思想:组合使用原型链和构造函数,使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现实例属性的继承。

function Super(name){
	this.name=name
}
Super.prototype.sayName=function(){
	console.log(this.name)
}
function Sub(name,age){
	Super.call(this,name)
	this.age=age
}

Sub.prototype=new Super()
Sub.prototype.constructor=Sub

var s1=new Sub("marry",23)
var s2=new Sub("mike",14)
s1.sayName()  //marry
s2.sayName()  //mike
s1.age //23

最常用的继承方式。缺点是会调用两次父类的构造函数,一次是在创建子类型原型时,一次是在子类型构造函数内部。

原型式继承

基本思想:基于已有的对象创建新对象,在函数中创建一个空的构造函数,该构造函数的原型等于传入的对象,最后返回该构造函数的实例。

function object(o){
	function F(){}
	F.prototype=o
	return new F()
}

ES5中使用Object.create()方法实现原型式继承,接收两个参数,一个是用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。

var person={
	name:'marry',
	age:12
}
var p=Object.create(person)
p.name  //marry

寄生式继承

创建一个用于封装继承过程的函数,在函数内部以某种方式来增强对象,最后返回该对象。

function createAnother(original){
	var clone=Object.create(original)
	clone.sayName=function(){
		console.log(this.name)
}
	return clone
}

var person={
	name:'mike'
}
var p=createAnother(person)
p.sayName()  //mike

寄生组合式继承

基本思想:通过构造函数来继承属性,通过原型链来继承方法。


function inheirt(subType,superType){
	var clone=Object.create(superType.prototype)  //创建父类型原型的一个副本
	clone.constructor=subType //指定原型的构造函数
	subType.prototype=clone //子类继承父类的原型属性和方法
}

function Super(name){
	this.name=name
	this.colors=['red','green']
	}
Super.prototype.sayName=function(){
	console.log(this.name)
}
function Sub(name,age){
	Super.call(this,name)  //继承构造函数的属性
	this.age=age
}

inheirt(Sub,Super)
//定义子类型的特有原型方法
Sub.prototype.sayAge=function(){
	console.log(this.age)
}

var s1=new Sub('zl',23)
s1.sayName() //zl
s1.sayAge() //23

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值