JS面向对象的程序设计相关知识小结


Posted in Javascript onMay 26, 2018

面向对象的语言有一个标志,即拥有类的概念,抽象实例对象的公共属性与方法,基于类可以创建任意多个实例对象,一般具有封装、继承、多态的特性!但JS中对象与纯面向对象语言中的对象是不同的,ECMA标准定义JS中对象:无序属性的集合,其属性可以包含基本值、对象或者函数。可以简单理解为JS的对象是一组无序的值,其中的属性或方法都有一个名字,根据这个名字可以访问相映射的值(值可以是基本值/对象/方法)。

一、理解对象:

第一种:基于Object对象

var person = new Object();
person.name = 'My Name';
person.age = 18;
person.getName = function(){
  return this.name;
}

第二种:对象字面量方式(比较清楚的查找对象包含的属性及方法)

var person = {
  name : 'My name',
  age : 18,
  getName : function(){
    return this.name;
  }
}

JS的对象可以使用‘.'操作符动态的扩展其属性,可以使用'delete'操作符或将属性值设置为'undefined'来删除属性。如下:

person.newAtt='new Attr';//添加属性
alert(person.newAtt);//new Attr
delete person.age;
alert(person.age);//undefined(删除属性后值为undefined);

二、对象属性类型

ECMA-262第5版定义了JS对象属性中特征(用于JS引擎,外部无法直接访问)。ECMAScript中有两种属性:数据属性和访问器属性

1、数据属性:

数据属性指包含一个数据值的位置,可在该位置读取或写入值,该属性有4个供述其行为的特性:

[[configurable]]:表示能否使用delete操作符删除从而重新定义,或能否修改为访问器属性。默认为true;

[[Enumberable]]:表示是否可通过for-in循环返回属性。默认true;

[[Writable]]:表示是否可修改属性的值。默认true;

[[Value]]:包含该属性的数据值。读取/写入都是该值。默认为undefined;如上面实例对象person中定义了name属性,其值为'My name',对该值的修改都反正在这个位置

要修改对象属性的默认特征(默认都为true),可调用Object.defineProperty()方法,它接收三个参数:属性所在对象,属性名和一个描述符对象(必须是:configurable、enumberable、writable和value,可设置一个或多个值)。

如下:(浏览器支持:IE9+、Firefox 4+、Chrome、Safari5+)

var person = {};
Object.defineProperty(person, 'name', {
  configurable: false,
  writable: false,
  value: 'Jack'
});
alert(person.name);//Jack
delete person.name;
person.name = 'lily';
alert(person.name);//Jack

可以看出,delete及重置person.name的值都没有生效,这就是因为调用defineProperty函数修改了对象属性的特征;值得注意的是一旦将configurable设置为false,则无法再使用defineProperty将其修改为true(执行会报错:can't redefine non-configurable property);

2、访问器属性:

它主要包括一对getter和setter函数,在读取访问器属性时,会调用getter返回有效值;写入访问器属性时,调用setter,写入新值;该属性有以下4个特征:

[[Configurable]]:是否可通过delete操作符删除重新定义属性;

[[Numberable]]:是否可通过for-in循环查找该属性;

[[Get]]:读取属性时调用,默认:undefined;

[[Set]]:写入属性时调用,默认:undefined;

访问器属性不能直接定义,必须使用defineProperty()来定义,如下:

var person = {
  _age: 18
};
Object.defineProperty(person, 'isAdult', {
  get: function () {
    if (this._age >= 18) {
      return true;
    } else {
      return false;
    }
  }
});
alert(person.isAdult?'成年':'未成年');//成年

从上面可知,定义访问器属性时getter与setter函数不是必须的,并且,在定义getter与setter时不能指定属性的configurable及writable特性;

此外,ECMA-262(5)还提供了一个Object.defineProperties()方法,可以用来一次性定义多个属性的特性:

var person = {};
Object.defineProperties(person,{
  _age:{
    value:19
  },
  isAdult:{
    get: function () {
      if (this._age >= 18) {
        return true;
      } else {
        return false;
      }
    }
  }
});
alert(person.isAdult?'成年':'未成年');//成年

上述代码使用Object.defineProperties()方法同时定义了_age及isAudlt两个属性的特性

此外,使用Object.getOwnPropertyDescriptor()方法可以取得给定属性的特性:

var descriptor = Object.getOwnPropertyDescriptor(person,'_age');
alert(descriptor.value);//19

对于数据属性,可以取得:configurable,enumberable,writable和value;

对于访问器属性,可以取得:configurable,enumberable,get和set

三、创建对象

使用Object构造函数或对象字面量都可以创建对象,但缺点是创建多个对象时,会产生大量的重复代码,因此下面介绍可解决这个问题的创建对象的方法

1、工厂模式

function createPerson(name, age, job) {
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.getName = function () {
    return this.name;
  }
  return o;//使用return返回生成的对象实例
}
var person = createPerson('Jack', 19, 'SoftWare Engineer');

创建对象交给一个工厂方法来实现,可以传递参数,但主要缺点是无法识别对象类型,因为创建对象都是使用Object的原生构造函数来完成的。

2、构造函数模式

function Person(name,age,job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.getName = function () {
    return this.name;
  }
}
var person1 = new Person('Jack', 19, 'SoftWare Engineer');

var person2 = new Person('Liye', 23, 'Mechanical Engineer');

使用自定义的构造函数(与普通函数一样,只是用它来创建对象),定义对象类型(如:Person)的属性和方法。它与工厂方法区别在于:

  • 没有显式地创建对象
  • 直接将属性和方法赋值给this对象;
  • 没有return语句;

此外,要创建Person的实例,必须使用new关键字,以Person函数为构造函数,传递参数完成对象创建;实际创建经过以下4个过程:

  1. 创建一个对象
  2. 将函数的作用域赋给新对象(因此this指向这个新对象,如:person1)
  3. 执行构造函数的代码
  4. 返回该对象

上述由Person构造函数生成的两个对象person1与person2都是Person的实例,因此可以使用instanceof判断,并且因为所有对象都继承Object,因此person1 instanceof Object也返回真:

alert(person1 instanceof Person);//true;
alert(person2 instanceof Person);//true;
alert(person1 instanceof Object);//true;
alert(person1.constructor === person2.constructor);//ture;

虽然构造函数方式比较不错,但也存在缺点,那就是在创建对象时,特别针对对象的属性指向函数时,会重复的创建函数实例,以上述代码为基础,可以改写为:

function Person(name,age,job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.getName = new Function () {//改写后效果与原代码相同,不过是为了方便理解
    return this.name;
  }
}

上述代码,创建多个实例时,会重复调用new Function();创建多个函数实例,这些函数实例还不是一个作用域中,当然这一般不会有错,但这会造成内存浪费。当然,可以在函数中定义一个getName = getName的引用,而getName函数在Person外定义,这样可以解决重复创建函数实例问题,但在效果上并没有起到封装的效果,如下所示:

function Person(name,age,job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.getName = getName;
}
function getName() {//到处是代码,看着乱!!
    return this.name;
}

3、原型模式

JS每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,它是所有通过new操作符使用函数创建的实例的原型对象。原型对象最大特点是,所有对象实例共享它所包含的属性和方法,也就是说,所有在原型对象中创建的属性或方法都直接被所有对象实例共享。

function Person(){
}
Person.prototype.name = 'Jack';//使用原型来添加属性
Person.prototype.age = 29;
Person.prototype.getName = function(){
  return this.name;
}
var person1 = new Person();
alert(person1.getName());//Jack
var person2 = new Person();
alert(person1.getName === person2.getName);//true;共享一个原型对象的方法

原型是指向原型对象的,这个原型对象与构造函数没有太大关系,唯一的关系是函数的prototype是指向这个原型对象!而基于构造函数创建的对象实例也包含一个内部指针为:[[prototype]]指向原型对象。

实例属性或方法的访问过程是一次搜索过程:

  • 首先从对象实例本身开始,如果找到属性就直接返回该属性值;
  • 如果实例本身不存在要查找属性,就继续搜索指针指向的原型对象,在其中查找给定名字的属性,如果有就返回;

基于以上分析,原型模式创建的对象实例,其属性是共享原型对象的;但也可以自己实例中再进行定义,在查找时,就不从原型对象获取,而是根据搜索原则,得到本实例的返回;简单来说,就是实例中属性会屏蔽原型对象中的属性;

原型与in操作符

一句话:无论原型中属性,还是对象实例的属性,都可以使用in操作符访问到;要想判断是否是实例本身的属性可以使用object.hasOwnProperty(‘attr')来判断;

原生对象中原型
原生对象中原型与普通对象的原型一样,可以添加/修改属性或方法,如以下代码为所有字符串对象添加去左右空白原型方法:

String.prototype.trim = function(){
  return this.replace(/^\s+/,'').replace(/\s+$/,'');
}
var str = '  word space  ';
alert('!'+str.trim()+'!');//!word space!

原型模式的缺点,它省略了为构造函数传递初始化参数,这在一定程序带来不便;另外,最主要是当对象的属性是引用类型时,它的值是不变的,总是引用同一个外部对象,所有实例对该对象的操作都会其它实例:

function Person() {
}
Person.prototype.name = 'Jack';
Person.prototype.lessons = ['Math','Physics'];
var person1 = new Person();
person1.lessons.push('Biology');
var person2 = new Person();
alert(person2.lessons);//Math,Physics,Biology,person1修改影响了person2

 4、组合构造函数及原型模式
目前最为常用的定义类型方式,是组合构造函数模式与原型模式。构造函数模式用于定义实例的属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方方法的引用,最大限度的节约内存。此外,组合模式还支持向构造函数传递参数,可谓是集两家之所长。

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.lessons = ['Math', 'Physics'];
}
Person.prototype = {
  constructor: Person,//原型字面量方式会将对象的constructor变为Object,此外强制指回Person
  getName: function () {
    return this.name;
  }
}
var person1 = new Person('Jack', 19, 'SoftWare Engneer');
person1.lessons.push('Biology');
var person2 = new Person('Lily', 39, 'Mechanical Engneer');
alert(person1.lessons);//Math,Physics,Biology
alert(person2.lessons);//Math,Physics
alert(person1.getName === person2.getName);//true,//共享原型中定义方法

在所接触的JS库中,jQuery类型的封装就是使用组合模式来实例的!!!

5、动态原型模式
组合模式中实例属性与共享方法(由原型定义)是分离的,这与纯面向对象语言不太一致;动态原型模式将所有构造信息都封装在构造函数中,又保持了组合的优点。其原理就是通过判断构造函数的原型中是否已经定义了共享的方法或属性,如果没有则定义,否则不再执行定义过程。该方式只原型上方法或属性只定义一次,且将所有构造过程都封装在构造函数中,对原型所做的修改能立即体现所有实例中:

function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.lessons = ['Math', 'Physics'];
}
if (typeof this.getName != 'function') {//通过判断实例封装
Person.prototype = {


constructor: Person,//原型字面量方式会将对象的constructor变为Object,此外强制指回Person


getName: function () {



return this.name;


}

}
}
var person1 = new Person('Jack', 19, 'SoftWare Engneer');
person1.lessons.push('Biology');
var person2 = new Person('Lily', 39, 'Mechanical Engneer');
alert(person1.lessons);//Math,Physics,Biology
alert(person2.lessons);//Math,Physics
alert(person1.getName === person2.getName);//true,//共享原型中定义方法

注:以上内容参考《JavaScript 高级程序设计》第3版

Javascript 相关文章推荐
情人节专属 纯js脚本1k大小的3D玫瑰效果
Feb 11 Javascript
Node.js 服务器端应用开发框架 -- Hapi.js
Jul 29 Javascript
jQuery图片轮播插件——前端开发必看
May 31 Javascript
分析JS中this引发的bug
Dec 12 Javascript
vue debug 二种方法
Sep 16 Javascript
vue-lazyload使用总结(推荐)
Nov 01 Javascript
Bootstrap 实现表格样式、表单布局的实例代码
Dec 09 Javascript
详解微信小程序用定时器实现倒计时效果
Apr 30 Javascript
koa2 从入门到精通(小结)
Jul 23 Javascript
Swiper.js实现移动端元素左右滑动
Sep 08 Javascript
使用vue制作滑动标签
Sep 21 Javascript
不依任何赖第三方,单纯用vue实现Tree 树形控件的案例
Sep 21 Javascript
JavaScript门道之标准库
May 26 #Javascript
javascript标准库(js的标准内置对象)总结
May 26 #Javascript
简单明了区分escape、encodeURI和encodeURIComponent
May 26 #Javascript
页面点击小红心js实现代码
May 26 #Javascript
js input输入百分号保存数据库失败的解决方法
May 26 #Javascript
使用javascript函数编写简单银行取钱存钱流程
May 26 #Javascript
jQuery插件jsonview展示json数据
May 26 #jQuery
You might like
杏林同学录(二)
2006/10/09 PHP
php trim 去除空字符的定义与语法介绍
2010/05/31 PHP
简单的自定义php模板引擎
2016/08/26 PHP
Laravel学习教程之View模块详解
2017/09/18 PHP
[原创]PHP global全局变量经典应用与注意事项分析【附$GLOBALS用法对比】
2019/07/12 PHP
javascript编程起步(第七课)
2007/02/27 Javascript
用jQuery实现检测浏览器及版本的脚本代码
2008/01/22 Javascript
jQuery登陆判断简单实现代码
2013/04/21 Javascript
javascript包装对象实例分析
2015/03/27 Javascript
JavaScript中的getDay()方法使用详解
2015/06/09 Javascript
详解JavaScript中双等号引起的隐性类型转换
2016/05/30 Javascript
移动端触屏幻灯片图片切换插件idangerous swiper.js
2017/04/10 Javascript
微信小程序之电影影评小程序制作代码
2017/08/03 Javascript
Mint UI组件库CheckList使用及踩坑总结
2018/12/20 Javascript
在vue使用clipboard.js进行一键复制文本的实现示例
2019/01/15 Javascript
JavaScript中的"=、==、==="区别讲解
2019/01/22 Javascript
解决包含在label标签下的checkbox在ie8及以下版本点击事件无效果兼容的问题
2019/10/27 Javascript
jQuery实现的图片点击放大缩小功能案例
2020/01/02 jQuery
vue登录页实现使用cookie记住7天密码功能的方法
2021/02/18 Vue.js
[44:51]2018DOTA2亚洲邀请赛 4.4 淘汰赛 VP vs Liquid 第二场
2018/04/05 DOTA
用Python编写一个简单的俄罗斯方块游戏的教程
2015/04/03 Python
python获取一组汉字拼音首字母的方法
2015/07/01 Python
使用Nginx+uWsgi实现Python的Django框架站点动静分离
2016/03/21 Python
python中类和实例如何绑定属性与方法示例详解
2017/08/18 Python
Python读取txt某几列绘图的方法
2018/10/14 Python
将python图片转为二进制文本的实例
2019/01/24 Python
python 进程的几种创建方式详解
2019/08/29 Python
感知器基础原理及python实现过程详解
2019/09/30 Python
解决margin 外边距合并问题
2019/07/03 HTML / CSS
Notino芬兰:购买香水和化妆品
2019/04/15 全球购物
中学生期末评语
2014/02/03 职场文书
高中生物教学反思
2014/02/05 职场文书
党员个人对照检查材料范文
2014/09/24 职场文书
农业项目投资意向书
2015/05/09 职场文书
《作风建设永远在路上》心得体会
2016/01/21 职场文书
业余无线电通联Q语
2022/02/18 无线电