对象(Object)应该算是js中最为重要的部分,也是js中非常难懂晦涩的一部分。更是面试以及框架设计中各出没。本文章,主要参考JavaScript红宝书(JavaScript高级程序设计 第六章)以及各大博主博客

谈谈对象属性的特性

  毕竟是面向对象编程,我们在讨论如何面向对象之前先讨论讨论对象具有哪些属性和特性。

属性类型

  简单的说,对象拥有四个属性:

  • [[Configurable]]:是否可以通过delete删除,能否修改属性的特性。直白点:是否可配置
  • [[Enumerable]]:枚举性,表示是否可以通过for-in循环返回
  • [[Writable]]:可写性:是否可以修改属性的值
  • [[Value]]:包含属性的值,也就是对应的可读性。 以上四个对象的属性的属性类型默认值分别为:true,true,true,undefined。

  如果要修改属性默认的特性,必须通过Object.defineProperty()方法。大致如下:

var animal = {};
Object.defineProperty(animal,"name",{
writable:false,
value: 'dog'
});
console.log(animal.name);//dog
animal.name = 'cat';
console.log(animal.name);//dog

  writable:false,表示不可更改属性的值。从上面的实例可以看出,在调用Object.defineProperty()方法后,如果不指定 configurable、enumerable、writable 特性的值时,默认为FALSE。

访问器属性

  访问器属性不包含数据值,但是包含getter和setter函数。在读取访问器属性时,会调用getter函数,这个函数负责返回有效值。在写入访问器属性时,回到用setter函数并传入新值。

  • [[Configurable]]:表示是否可以通过delete删除。默认为TRUE
  • [[Enumerable]]:同上面介绍的Enumerable一样,默认为true
  • [[Get]]:读取数据时候调用的方法。默认为undefined
  • [[Set]]:在写入属性值得时候默认调用的方法。默认为undefined

  这里不做过多解释,直接看例子吧(来自js红宝书)

var book = {
_year:,
edition:
};
Object.defineProperty(book, 'year',{
get:function(){
return this._year
},
set:function(value){
if(value>){
this._year = value,
this.edition++
}
}
}); book.year = ;
console.log(book.edition);//

  其实对于多个属性的定义,我们可以使用Object.defineProperties方法。然后对于读取属性的特性我们可以使用Object.getOwnPropertyDescriptor()方法。

创建对象

  创建对象,我们不是直接可以通过Object的构造函数或者对象字面量的方法来实现对象的创建嘛?当然,这些方法是可以的,但是有一个明显的缺点:使用同一个接口创建很多对象,产生大量重复的代码。所以这里,我们使用如下的一些操作

工厂模式

  一种很基础的设计模式,简而言之就是用函数来封装以特定接口创建对象的细节。

function createAnimal(name,type){
var o = new Object();
o.name = name;
o.type = type;
o.sayName = function(){
alert(this.name)
}
return o;
}
var cat = createAnimal('小猫','cat');
var dog = createAnimal('小狗','dog');

  优点:可以无数次的调用这个函数,来创建相似对象。

  缺点:不能解决对象识别的问题。也就是说,我不知道你是谁家的b孩子

构造函数模式

  ECMAScript中的构造函数可以用来创建特定类型的对象。在运行时会自动出现在执行环境中(这句话后面讲解this的时候还是会说到)。

function Animal(name,type){
this.name = name;
this.type = type;
this.say = function(){
alert(this.name);
}
} var cat = new Animal('小猫','cat');
var dog = new Animal('小狗','dog');

  注意上面我们没有显示的return过一个对象出来,为什么?因为this(后面会讲this的)。

  关于构造函数惯例首字母大写就不啰嗦了。强调构造函数一定要使用关键字new来调用。为什么使用new呢?因为你使用了new,他会

  • 创建一个新的对象
  • 将构造函数的作用域赋值给新对象(this执行新的对象)
  • 执行构造函数的代码
  • 返回新的对象

  那么解决了工厂模式的诟病了么?当然~

  在实例对象中,都有一个constructor属性。

cat.constructor == Animal //true
dog.constructor == Animal //true
cat instanceof Animal //true
dog instanceof Animal //true

  构造函数模式的优点如上所说,但是缺点还是有的,比如说

cat.sayName == dog.sayName //false

  也就是说,他创建了两个功能一样的函数,这样是很没有必要的,当然,我们可以把sayName放到构造函数外面,然后通过this.sayName=sayName来操作,但是这样的话,又会导致全局变量的污染。肿么办???

原型模式

  我们在创建每一个函数的时候都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象。而这个对象的用途就是包含由特定类型的所有实例共享的属性和方法。

function Animal() {}
Animal.prototype.name = '毛毛';
Animal.prototype.type = 'dog';
Animal.prototype.sayName = function() {
alert(this.name);
}
var cat = new Animal();
var dog = new Animal();
alert(cat.sayName == dog.sayName)//true

  原型模式的好处就是可以让所有的对象实例共享他的属性和方法。不必在构造函数中定义对象实例的信息。

function Person() {}
Person.prototype.name = 'Nealyang';
Person.prototype.age = ;
Person.prototype.sayName = function(){
alert(this.name);
}
var neal = new Person();
console.log(neal.name)//'Nealyang' -> 来自原型
neal.name = 'Neal';
console.log(neal.name)// Neal -> 来自实例 delete neal.name;
console.log(neal.name)//'Nealyang' -> 来自原型

  上面的例子说明两点

  • 原型中的对象属性可以被实例所覆盖重写
  • 通过delete可以删除实例中的属性,但是删除不了对象上的

我们可以通过hasOwnProperty()方法来确定一个属性是在原型上还是在实例上。person1.hasOwnProperty('name'),如果name为实例属性,则返回true。 我们也可以通过 'name' in person1 来确定,person1上是否有name这个属性。

  上面大家可能已将发现,这种原型模式的写法非常的繁琐,有了大量的XXX.prototype. 这里有一种简写的形式。

function Person(){}
Person.prototype = {
constructor:Person,
name:"Neal",
age:,
job:'Software Engineer',
sayName:function(){
alert(this.name);
}
}

  上面代码特意添加了一个constructor属性,因为每创建一个函数,就会自动创建他的prototype对象,这个对象会自动获取contractor属性。而我们这中写法,本质上重写了默认的prototype对象,因此,constructor属性也就变成新的对象的constructor属性了(指向Object构造函数),所以这里的简写方式,一定要加上constructor。

  下面我们再谈一谈原型模式的优缺点。

  优点,正如上面我们说到的,可以省略为构造函数传递出实话参数这个环节,并且很多实例可以共享属性和方法。

  正是因为原型中所有的属性是被所有的实例所共享的,这个特性在方法中非常实用,但是对于包含引用类型的属性来说问题就比较突出了。

function Person(){};

Person.prototype = {
constructor:Person,
name:"neal",
friends:['xiaohong','xiaoming'],
sayName:function(){
alert(this.name);
}
} var person1 = new Person();
var person2 = new Person(); person1.friends.push('xiaohua'); alert(person1.friends);//'xiaohong','xiaoming','xiaohua'
alert(person2.friends);//'xiaohong','xiaoming','xiaohua'
alert(person1.friends == person2.friends)//true

  由于friends数组存在于Person.prototype上,并不是person1上面,所以当我们修改的时候,其实修改的是所有实例所共享的那个值

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

  这是创建自定义类型最常见的一种方式。就是组合使用构造函数和原型模式.构造函数模式用于定义实力属性,原型模式用于定义方法和共享的属性。

function Person(name,age){
this.name = name,
this.age = age
} Person.prototype = {
constructor:Person,
sayName:function(){
alert(this.name);
}
} var person1 = new Person('Neal',);
var person2 = new Person('Yang',);
...

  上面的例子中,实例所有的属性都是在构造函数中定义,而实例所有共享的属性和方法都是在原型中定义。这种构造函数和原型模式混合的模式,是目前ECMAScript中使用最为广泛的一种方法。

  当然,有些人会觉得独立的构造函数和原型非常的难受,所以也有推出所谓的动态原型构造模式的这么一说。

function Person(name,age){
this.name = name,
this.age = age,
if(typeof this.sayName != 'function'){
Person.prototype.sayName = function(){
console.log(this.name)
}
}
}
...

  注意上面的代码,之后在sayName不存在的时候,才会在原型上给他添加相应的方法。因为对原型的修改,能够立即在所有的实例中得到反应。所以这中做法确实也是非常的完美。

继承

  说到面向对象,当然得说到继承。说到继承当然得说到原型。

  为了说明javascript是一门面向对象的语言,首先有必要从面相对象的概念入手:1、一切事物皆对象。2、对象具有封装和继承特性。3、对象与对象之间使用消息通信,各自存在信息隐秘 。 javascript语言是通过一种叫做原型(prototype) 的方式来实现面向对象编程的。当然,还有比如java就是基于类来实现面向对象编程的。

基于类的面向对象和基于原型的面向对象方式比价

  对于基于类的面向对象的方式中,对象依靠class类来产生。而在基于原型的面向对象方式中,对象则是依靠构造器(constructor)利用原型(prototype)构造出来的。举个客观世界的例子来说,例如工厂造一辆汽车一方面,工人必须参照一张工程图纸,设计规定这辆车如何制造,这里的工程图纸就好比语言中的类class。而车就是按照这个类制造出来的。另一方面,工人和机器相当于contractor,利用各种零部件(prototype)将汽车造出来。

  当然,对于上面的例子两种思维各种说法。当然,笔者更加倾向于基于原型的面向对象编程,毕竟我是前端出生,正当理由如下:

  首先,客观世界中的对象的产生都是其他实物对象构造的世界,而抽象的图纸是不能产生出汽车的。也就是说,类,是一个抽象的概念而非实体,而对象的产生是一个实体的产生。其次,按照一切事物皆对象的这种面向对象的法则来说,类本身并不是一个对象,然而原型方式的构造函数和原型本身也是个对象。再次,在类的面向对象语言中,对象的状态由对象的实例所持有,对象的行为方法则由申明该对象的类所持有,并且只有对象的构造和方法能够被继承。而在原型的面向对象语言中,对象的行为、状态都属于对象本身,并且能够一起被继承。

原型链

  ECMAScript描述了原型链的概念,并将原型链作为实现继承的主要方法。基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法。

  实现原型链有一种基本模式:

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(); alert(instance.getSuperValue());//true

  在上面的代码中,我们没有使用SubType默认提供的原型,而是给它换了一个新的原型,这个新原型就是SuperType的实例。于是,新原型不仅具有所谓一个SuperType的实例所拥有的全部属性和方法,而且其内部还有一个指针,指向SuperType的原型。最终结果是这样的:instance指向subtype的原型,subtype的原型又指向SuperType的原型。

  通过实现原型链,本质上是扩展了原型搜索机制。

  虽然如上,我们已经实现了javascript中的继承。但是依旧存在一些问题:最主要的问题来自包含引用类型的原型。第二个问题就是在创建子类型的实例时,不能向超类型的构造函数中传递参数。这两个问题上面也都有说到,这里就不做过多介绍,直接看解决办法!

借用构造函数

  在解决原型中包含引用类型的数据时,我们可以在子类型构造函数内部调用超类型的构造函数。直接看代码:

function SuperType(name){
this.colors = ['red','yellow'];
this.name = name;
} function SubType(name){
//继承了Super
SuperType.call(this,name)
} var instance1 = new SubType('Neal');
alert(instance1.name)
instance1.colors.push('black');
alert(instance1.colors);//'red','yellow','black' var instance2 = new SubType('yang');
alert(instance2.colors);//'red','yellow'

  毕竟函数只不过是在特定环境中执行代码的对象,因此可以通过call或者apply方法在新创建的对象上执行构造函数。而且如上代码也解决了子类构造函数中向超类构造函数传递参数的问题

  但是,这样问题就来了,类似我们之前讨论创建的对象那种构造函数的问题:如果都是使用构造函数,那么,也就避免不了方法都在构造函数中定义,然后就会产生大量重复的代码了。

组合继承

  因为考虑到上述的缺点,所以这里又使用了组合继承的方式,历史总是惊人的相似。直接看代码:

function SuperType(name){
this.name = name;
this.colors = ['red','yellow'];
} SuperType.prototype.sayName = function(){
alert(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(){
alert(this.age);
} var instance1 = new SubType('Nealyang',);
instance1.colors.push('white');
alert(instance1.colors);//'red','yellow','white'
instance1.sayName();//Nealyang
instance1.sayAge();// var instance2 = new SubType('Neal',);
alert(instance2.colors);//'red','yellow'
instance2.sayName();//Neal
instance2.sayAge();//

  在上面的例子中:SuperType构造函数定义了两个属性,name和colors,SuperType的原型中定义了一个方法sayName,subtype的构造函数中调用SuperType构造函数并且传入name,然后将SuperType的实例赋值给subtype的原型。然后又在新的原型中定义了sayAge的方法。这样一来,就可以让两个不同的SubType实例既分别拥有自己的属性,包括colors,又可以使用相同的方法了。

  组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优点。成为javascript中最为常见的继承模式。而且instanceof和isPrototypeOf方法也能用于识别组合模式创建的对象。

JavaScript面向对象总结的更多相关文章

  1. JavaScript学习笔记(三)——this、原型、javascript面向对象

    一.this 在JavaScript中this表示:谁调用它,this就是谁. JavaScript是由对象组成的,一切皆为对象,万物皆为对象.this是一个动态的对象,根据调用的对象不同而发生变化, ...

  2. JavaScript学习总结(三)——this、原型、javascript面向对象

    一.this 在JavaScript中this表示:谁调用它,this就是谁. JavaScript是由对象组成的,一切皆为对象,万物皆为对象.this是一个动态的对象,根据调用的对象不同而发生变化, ...

  3. Javascript面向对象特性实现封装、继承、接口详细案例——进级高手篇

    Javascript面向对象特性实现(封装.继承.接口) Javascript作为弱类型语言,和Java.php等服务端脚本语言相比,拥有极强的灵活性.对于小型的web需求,在编写javascript ...

  4. javascript面向对象系列第四篇——选项卡的实现

    前面的话 面向对象的应用并非只是读几本书那么容易,需要有大量的工程实践做基础才能真正理解并学会使用它.本文将用面向对象的技术来制作一个简单的选项卡 图示说明 由图示结果看到,这是一个非常简单的选项卡. ...

  5. javascript面向对象系列第三篇——实现继承的3种形式

    × 目录 [1]原型继承 [2]伪类继承 [3]组合继承 前面的话 学习如何创建对象是理解面向对象编程的第一步,第二步是理解继承.本文是javascript面向对象系列第三篇——实现继承的3种形式 [ ...

  6. javascript面向对象系列第一篇——构造函数和原型对象

    × 目录 [1]构造函数 [2]原型对象 [3]总结 前面的话 一般地,javascript使用构造函数和原型对象来进行面向对象编程,它们的表现与其他面向对象编程语言中的类相似又不同.本文将详细介绍如 ...

  7. Javascript面向对象(封装、继承)

    Javascript 面向对象编程(一):封装 作者:阮一峰 Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象.但是,它又不是一种真正的面向对象编程( ...

  8. 《javascript面向对象精要》读书笔记

    <javascript面向对象精要> 买这本书的原因主要是因为作者,Nicholas C. Zakas 牛X闪闪的js专家,读过js高程的应该都知道他,而这本书是他的最新力作,感觉也是js ...

  9. 【转】javascript面向对象编程

    摘要:本文本来是想自己写的,奈何花了好长时间写好之后忘记保存,还按了刷新键,一键回到解放前,索性不写了,所以本文是转载的. 面向对象编程是用抽象方式创建基于现实世界模型的一种编程模式,主要包括模块化. ...

  10. javascript面向对象(一):封装

    本文来自阮一峰 学习Javascript,最难的地方是什么? 我觉得,Object(对象)最难.因为Javascript的Object模型很独特,和其他语言都不一样,初学者不容易掌握. 下面就是我的学 ...

随机推荐

  1. Google Breakpad 之一,跨平台crash 处理上报系统简介

    Google Breakpad 之一,跨平台crash 处理上报系统简介 http://blog.csdn.net/wpc320/article/details/8290501 Google Brea ...

  2. 获取span中的值

    js-code:var baseinfoallfeesum=$("#allFeeSum").html(); $("#baseInfoAllFeeSum").va ...

  3. C#实例:Unity依赖注入使用

    http://jingyan.baidu.com/article/c74d6000840b260f6b595d78.html

  4. k8s job的使用

    1.运行一次性容器 容器按照持续运行的时间可分为两类: 服务类容器 服务类容器通常持续提供服务,需要一直运行,比如 http server,daemon 等. 工作类容器 工作类容器则是一次性任务,比 ...

  5. 《锋利的jQuery》读书要点笔记6——制作商城网页:结构和样式设计

    第8章 用jQuery打造个性网站 做一个购物网站并用jQuery来完善.大体步骤是: 收集素材 确定网站结构 A. 文件结构,imagea文件夹用来存放将要用到的图片,styles文件夹存放CSS, ...

  6. spring 整合Junit

    主要参考: https://www.ibm.com/developerworks/cn/java/j-lo-springunitest/ http://www.cnblogs.com/rainisic ...

  7. MySQL的事务理解

    在学习事务这一概念前,我们需要需要构思一个场景 场景构思 假设该场景发生于一个银行转账背景下,月中,又到了发工资的日子.学校打算给A老师发放一个月的工资.(此处,我们假设转账都是由人工操作的),整个过 ...

  8. 使用graphql和apollo client构建react web应用

    graphql是一种用于 API 的查询语言(摘自官网). 我们为什么要用graphql? 相信大家在开发web应用的时候常常会遇到以下这些问题:后端更新了接口却没有通知前端,从而导致各种报错:后端修 ...

  9. 浙江省第十二届省赛 Beauty of Array(思维题)

    Description Edward has an array A with N integers. He defines the beauty of an array as the summatio ...

  10. HDU 1039.Easier Done Than Said?-条件判断字符串

    Easier Done Than Said? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/O ...