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应用之禁止抓屏、复制、打印
Feb 21 Javascript
jQuery 连续列表实现代码
Dec 21 Javascript
javascript实现点击提交按钮后显示loading的方法
Jul 03 Javascript
Javascript中apply、call、bind的巧妙使用
Aug 18 Javascript
H5手机端多文件上传预览插件
Apr 21 Javascript
angular.js指令中的controller、compile与link函数的不同之处
May 10 Javascript
javascript 开发之网页兼容各种浏览器
Sep 28 Javascript
JQuery 又谈ajax局部刷新
Nov 27 jQuery
vue组件详解之使用slot分发内容
Apr 09 Javascript
vue中keep-alive,include的缓存问题
Nov 26 Javascript
深入浅析golang zap 日志库使用(含文件切割、分级别存储和全局使用等)
Feb 19 Javascript
JS实现网站楼层导航效果代码实例
Jun 16 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
php通过COM类调用组件的实现代码
2012/01/11 PHP
54个提高PHP程序运行效率的方法
2015/07/19 PHP
PHP中模糊查询并关联三个select框
2017/06/19 PHP
别了 JavaScript中的isXX系列
2012/08/01 Javascript
jQuery显示和隐藏 常用的状态判断方法
2015/01/29 Javascript
js给网页加上背景音乐及选择音效的方法
2015/03/03 Javascript
jquery实现华丽的可折角广告代码
2015/09/02 Javascript
jQuery.uploadify文件上传组件实例讲解
2016/09/23 Javascript
javascript 数组去重复(在线去重工具)
2016/12/17 Javascript
利用BootStrap的Carousel.js实现轮播图动画效果
2016/12/21 Javascript
nodejs基础应用
2017/02/03 NodeJs
在 Linux/Unix 中不重启 Vim 而重新加载 .vimrc 文件的流程
2018/03/21 Javascript
vue实现2048小游戏功能思路详解
2018/05/09 Javascript
JS双向链表实现与使用方法示例(增加一个previous属性实现)
2019/01/31 Javascript
关于layui flow loading占位图的实现方法
2019/09/21 Javascript
vue-router之解决addRoutes使用遇到的坑
2020/07/19 Javascript
[04:16]DOTA2英雄梦之声_第09期_斧王
2014/06/21 DOTA
python网络编程学习笔记(四):域名系统
2014/06/09 Python
Linux下将Python的Django项目部署到Apache服务器
2015/12/24 Python
Python实现的KMeans聚类算法实例分析
2018/12/29 Python
对Python发送带header的http请求方法详解
2019/01/02 Python
python使用for循环计算0-100的整数的和方法
2019/02/01 Python
python基于socket进行端口转发实现后门隐藏的示例
2019/07/25 Python
python自动化测试无法启动谷歌浏览器问题
2019/10/10 Python
python实现超市商品销售管理系统
2019/10/25 Python
python 通过视频url获取视频的宽高方式
2019/12/10 Python
python实现梯度法 python最速下降法
2020/03/24 Python
如何使用python记录室友的抖音在线时间
2020/06/29 Python
会计应聘求职信范文
2013/12/17 职场文书
《翻越远方的大山》教学反思
2014/04/13 职场文书
优秀驾驶员先进事迹材料
2014/05/04 职场文书
销售团队获奖感言
2014/08/14 职场文书
2016年大学生寒假社会实践心得体会
2015/10/09 职场文书
导游词之南京栖霞山
2019/10/18 职场文书
《辉夜大小姐想让我告白》第三季正式预告
2022/03/20 日漫
windows10声卡驱动怎么安装?win10声卡驱动安装操作步骤教程
2022/08/05 数码科技