JavaScript编程中实现对象封装特性的实例讲解


Posted in Javascript onJune 24, 2016

1.prototype对象
1.1构造函数的缺点
JavaScript通过构造函数生成新对象,因此构造函数可以视为对象的模板。实例对象的属性和方法,可以定义在构造函数内部。

function Cat (name, color) {
 this.name = name;
 this.color = color;
}

var cat1 = new Cat('大毛', '白色');

cat1.name // '大毛'
cat1.color // '白色'

上面代码的Cat函数是一个构造函数,函数内部定义了name属性和color属性,所有实例对象都会生成这两个属性。但是,这样做是对系统资源的浪费,因为同一个构造函数的对象实例之间,无法共享属性。

function Cat(name, color) {
 this.name = name;
 this.color = color;
 this.meow = function () {
 console.log('mew, mew, mew...');
 };
}

var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');

cat1.meow === cat2.meow
// false

上面代码中,cat1和cat2是同一个构造函数的实例。但是,它们的meow方法是不一样的,就是说每新建一个实例,就会新建一个meow方法。这既没有必要,又浪费系统资源,因为所有meow方法都是同样的行为,完全应该共享。

1.2 prototype属性的作用
在JavaScript语言中,每一个对象都有一个对应的原型对象,被称为prototype对象。定义在原型对象上的所有属性和方法,都能被派生对象继承。这就是JavaScript继承机制的基本设计。

除了这种方法,JavaScript还提供了另一种定义实例对象的方法。我们知道,构造函数是一个函数,同时也是一个对象,也有自己的属性和方法,其中有一个prototype属性指向另一个对象,一般称为prototype对象。该对象非常特别,只要定义在它上面的属性和方法,能被所有实例对象共享。也就是说,构造函数生成实例对象时,自动为实例对象分配了一个prototype属性。

function Animal (name) {
 this.name = name;
}

Animal.prototype.color = "white";

var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');

cat1.color // 'white'
cat2.color // 'white'

上面代码对构造函数Animal的prototype对象,添加了一个color属性。结果,实例对象cat1和cat2都带有该属性。

更特别的是,只要修改prototype对象,变动就立刻会体现在实例对象。

Animal.prototype.color = "yellow";

cat1.color // 'yellow'
cat2.color // 'yellow'

上面代码将prototype对象的color属性的值改为yellow,两个实例对象的color属性的值立刻就跟着变了。这是因为实例对象其实没有color属性,都是读取prototype对象的color属性。也就是说,当实例对象本身没有某个属性或方法的时候,它会到构造函数的prototype对象去寻找该属性或方法。这就是prototype对象的特殊之处。

如果实例对象自身就有某个属性或方法,它就不会再去prototype对象寻找这个属性或方法。

cat1.color = 'black';

cat2.color // 'yellow'
Animal.prototype.color // "yellow";

上面代码将实例对象cat1的color属性改为black,就使得它不用再去prototype对象读取color属性,后者的值依然为yellow。

总而言之,prototype对象的作用,就是定义所有实例对象共享的属性和方法,所以它也被称为实例对象的原型,而实例对象可以视作从prototype对象衍生出来的。

Animal.prototype.walk = function () {
 console.log(this.name + ' is walking.');
};

上面代码在Animal.protype对象上面定义了一个walk方法,这个方法将可以在所有Animal实例对象上面调用。

1.3原型链
由于JavaScript的所有对象都有构造函数,而所有构造函数都有prototype属性(其实是所有函数都有prototype属性),所以所有对象都有自己的prototype原型对象。

因此,一个对象的属性和方法,有可能是定义它自身上面,也有可能定义在它的原型对象上面(就像上面代码中的walk方法)。由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype chain)。比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。因为追根溯源,最源头的对象都是从Object构造函数生成(使用new Object()命令),所以如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype。那么,Object.prototype有没有原型呢?回答可以是有,也可以是没有,因为Object.prototype的原型是没有任何属性和方法的null。

Object.getPrototypeOf(Object.prototype)
// null

上面代码表示Object.prototype对象的原型是null,由于null没有任何属性,所以原型链到此为止。

“原型链”的作用在于,当读取对象的某个属性时,JavaScript引擎先寻找对象本身的属性,如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。以此类推,如果直到最顶层的Object.prototype还是找不到,则返回undefined。

举例来说,如果让某个函数的prototype属性指向一个数组,就意味着该函数可以用作数组的构造函数,因为它生成的实例对象都可以通过prototype属性调用数组方法。

function MyArray (){}

MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;

var mine = new MyArray();
mine.push(1, 2, 3);

mine.length // 3
mine instanceof Array // true

上面代码的mine是MyArray的实例对象,由于MyArray的prototype属性指向一个数组,使得mine可以调用数组方法(这些方法其实定义在数组的prototype对象上面)。至于最后那行instanceof表达式,我们知道instanceof运算符用来比较一个对象是否为某个构造函数的实例,最后一行表示mine为Array的实例。

mine instanceof Array

// 等同于

(Array === MyArray.prototype.constructor) ||
(Array === Array.prototype.constructor) ||
(Array === Object.prototype.constructor )

上面代码说明了instanceof运算符的实质,它依次与实例对象的所有原型对象的constructor属性(关于该属性的介绍,请看下一节)进行比较,只要有一个符合就返回true,否则返回false。

1.4 constructor属性
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。

function P() {}

P.prototype.constructor === P
// true

由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。

function P() {}

var p = new P();

p.constructor
// function P() {}

p.constructor === P.prototype.constructor
// true

p.hasOwnProperty('constructor')
// false

上面代码表示p是构造函数P的实例对象,但是p自身没有contructor属性,该属性其实是读取原型链上面的P.prototype.constructor属性。

constructor属性的作用是分辨prototype对象到底定义在哪个构造函数上面。

function F(){};

var f = new F();

f.constructor === F // true
f.constructor === RegExp // false

上面代码表示,使用constructor属性,确定变量f的构造函数是F,而不是RegExp。

2.Object.getPrototypeOf方法
Object.getPrototypeOf方法返回一个对象的原型。

// 空对象的原型是Object.prototype
Object.getPrototypeOf({}) === Object.prototype
// true

// 函数的原型是Function.prototype
function f() {}
Object.getPrototypeOf(f) === Function.prototype
// true

// 假定F为构造函数,f为F的实例对象
// 那么,f的原型是F.prototype
var f = new F();
Object.getPrototypeOf(f) === F.prototype
// true

3.Object.create方法
Object.create方法用于生成新的对象,可以替代new命令。它接受一个对象作为参数,返回一个新对象,后者完全继承前者的属性,即前者成为后者的原型。

var o1 = { p: 1 };
var o2 = Object.create(o1);

o2.p // 1

上面代码中,Object.create方法在o1的基础上生成了o2。此时,o1成了o2的原型,也就是说,o2继承了o1所有的属性的方法。

Object.create方法基本等同于下面的代码,如果老式浏览器不支持Object.create方法,可以用下面代码自己部署。

if (typeof Object.create !== "function") {
 Object.create = function (o) {
 function F() {}
 F.prototype = o;
 return new F();
 };
}

上面代码表示,Object.create方法实质是新建一个构造函数F,然后让F的prototype属性指向作为原型的对象o,最后返回一个F的实例,从而实现让实例继承o的属性。

下面三种方式生成的新对象是等价的。

var o1 = Object.create({});
var o2 = Object.create(Object.prototype);
var o3 = new Object();

如果想要生成一个不继承任何属性(比如toString和valueOf方法)的对象,可以将Object.create的参数设为null。

var o = Object.create(null);

o.valueOf()
// TypeError: Object [object Object] has no method 'valueOf'

上面代码表示,如果对象o的原型是null,它就不具备一些定义在Object.prototype对象上面的属性,比如valueOf方法。

使用Object.create方法的时候,必须提供对象原型,否则会报错。

Object.create()
// TypeError: Object prototype may only be an Object or null

Object.create方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,会立刻反映在新对象之上。

var o1 = { p: 1 };
var o2 = Object.create(o1);

o1.p = 2;
o2.p
// 2

上面代码表示,修改对象原型会影响到新生成的对象。

除了对象的原型,Object.create方法还可以接受第二个参数,表示描述属性的attributes对象,跟用在Object.defineProperties方法的格式是一样的。它所描述的对象属性,会添加到新对象。

var o = Object.create(Object.prototype, {
 p1: { value: 123, enumerable: true },
 p2: { value: "abc", enumerable: true }
});

o.p1 // 123
o.p2 // "abc"

由于Object.create方法不使用构造函数,所以不能用instanceof运算符判断,对象是哪一个构造函数的实例。这时,可以使用下面的isPrototypeOf方法,判读原型是哪一个对象。

4.isPrototypeOf方法
isPrototypeOf方法用来判断一个对象是否是另一个对象的原型。

var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);

o2.isPrototypeOf(o3) // true
o1.isPrototypeOf(o3) // true

上面代码表明,只要某个对象处在原型链上,isProtypeOf都返回true。

5.一个简单示例

var ClassDemo = function () {

 //静态private变量
 var private_static_var = 'aaaa';

 //静态private方法
 var private_static_func = function (key) { 
  return key + private_static_var;
 }

 //private方法,关键就是第一参数self要传入this
 var private_func = function (self, key) { 
  return private_static_func(key + self.id);
 }

 var _class = function (id) { //构造函数
  this.id = id; //public变量
 }

 //public方法
 _class.prototype.public_func = function (key) { 
  return private_func(this, key);
 }

 return _class;

}();

var a = new ClassDemo('hello world');
alert(a.public_func('world hello'));

没有简单的办法实现private变量和public静态变量/方法,不过封装到这个程度够用了

Javascript 相关文章推荐
奉献给JavaScript初学者的编写开发的七个细节
Jan 11 Javascript
js querySelector和getElementById通过id获取元素的区别
Apr 20 Javascript
js网页中的(运行代码)功能实现思路
Feb 04 Javascript
php与js的区别是什么
Aug 05 Javascript
用jquery方法操作radio使其默认选项是否
Sep 10 Javascript
通过length属性判断jquery对象是否存在
Oct 18 Javascript
BootStrap点击下拉菜单项后显示一个新的输入框实现代码
May 16 Javascript
基于jQuery的checkbox全选问题分析
Nov 18 Javascript
element-ui upload组件多文件上传的示例代码
Oct 17 Javascript
通过js示例讲解时间复杂度与空间复杂度
Aug 06 Javascript
实用的 vue tags 创建缓存导航的过程实现
Dec 03 Vue.js
如何使用JavaScript策略模式校验表单
Apr 29 Javascript
JS全局变量和局部变量最新解析
Jun 24 #Javascript
jQuery插件passwordStrength密码强度指标详解
Jun 24 #Javascript
jquery选择器中的空格与大于号>、加号+与波浪号~的区别介绍
Jun 24 #Javascript
jquery表单插件Autotab使用方法详解
Jun 24 #Javascript
jQuery插件cxSelect多级联动下拉菜单实例解析
Jun 24 #Javascript
jQuery下拉框的简单应用
Jun 24 #Javascript
浅谈json取值(对象和数组)
Jun 24 #Javascript
You might like
聊天室php&mysql(一)
2006/10/09 PHP
PHP中的extract的作用分析
2008/04/09 PHP
php创建和删除目录函数介绍和递归删除目录函数分享
2014/11/18 PHP
PHP实现的通过参数生成MYSQL语句类完整实例
2016/04/11 PHP
php中的单引号、双引号和转义字符详解
2017/02/16 PHP
php防止表单重复提交实例讲解
2019/02/11 PHP
让焦点自动跳转
2006/07/01 Javascript
js将long日期格式转换为标准日期格式实现思路
2013/04/07 Javascript
基于编写jQuery的无缝滚动插件
2014/08/02 Javascript
纯javascript实现自动发送邮件
2015/10/21 Javascript
js正则表达式replace替换变量方法
2016/05/21 Javascript
几种二级联动案例(jQuery\Array\Ajax php)
2016/08/13 Javascript
输入框点击时边框变色效果的实现方法
2016/12/26 Javascript
js date 格式化
2017/02/15 Javascript
Angular.Js中ng-include指令的使用与实现
2017/05/07 Javascript
es6 字符串String的扩展(实例讲解)
2017/08/03 Javascript
Three.js开发实现3D地图的实践过程总结
2017/11/20 Javascript
在vue中使用Echarts利用watch做动态数据渲染操作
2020/07/20 Javascript
微信小程序基于高德地图API实现天气组件(动态效果)
2020/10/22 Javascript
js实现自定义滚动条的示例
2020/10/27 Javascript
通过实例了解Render Props回调地狱解决方案
2020/11/04 Javascript
详解Python中列表和元祖的使用方法
2015/04/25 Python
Python实现针对给定单链表删除指定节点的方法
2018/04/12 Python
python实现大文件分割与合并
2019/07/22 Python
Python函数的返回值、匿名函数lambda、filter函数、map函数、reduce函数用法实例分析
2019/12/26 Python
Tensorflow 卷积的梯度反向传播过程
2020/02/10 Python
Spring Boot中使用IntelliJ IDEA插件EasyCode一键生成代码详细方法
2020/03/20 Python
基于python实现百度语音识别和图灵对话
2020/11/02 Python
pandas将list数据拆分成行或列的实现
2020/12/13 Python
python上下文管理器异常问题解决方法
2021/02/07 Python
CSS3实现跳动的动画效果
2016/09/12 HTML / CSS
美国女性服饰销售网站:Nasty Gal(坏女孩)
2016/07/26 全球购物
世界上最大的折扣香水店:FragranceNet.com
2016/10/26 全球购物
极度干燥澳大利亚官方网站:Superdry澳大利亚
2019/03/28 全球购物
创业项目大全(适合在家创业的项目)
2019/08/15 职场文书
python 开心网和豆瓣日记爬取的小爬虫
2021/05/29 Python