《JavaScript设计模式》——2.2 包装明星——封装

2.2 包装明星——封装

2.2.1 创建一个类
“在JavaScript中创建一个类很容易,首先声明一个函数保存在一个变量里。按编程习惯一般将这个代表类的变量名首字母大写。然后在这个函数(类)的内部通过对this(函数内部自带的一个变量,用于指向当前这个对象)变量添加属性或者方法来实现对类添加属性或者方法,例如:”

var Book = function(id, bookname, price){
  this.id = id;
  this.bookname= bookname;
  this.price = price;
}

“也可以通过在类的原型(类也是一个对象,所以也有原型prototype)上添加属性和方法,有两种方式,一种是一一为原型对象属性赋值,另一种则是将一个对象赋值给类的原型对象。但这两种不要混用。例如:”

Book.prototype.display = function(){
  // 展示这本书
};
或者

Book.prototype = {
  display : function(){}
};

“这样我们将所需要的方法和属性都封装在我们抽象的Book类里面了,当使用功能方法时,我们不能直接使用这个Book类,需要用new关键字来实例化(创建)新的对象。使用实例化对象的属性或者方法时,可以通过点语法访问,例如:”

var book = new Book(10, 'JavaScript设计模式', 50);
console.log(book.bookname) // JavaScript设计模式

小白看了看对类添加的属性和方法部分,感觉不是很理解,于是问:“通过this添加的属性和方法同在prototype中添加的属性和方法有什么区别呀?”

“通过this添加的属性、方法是在当前对象上添加的,然而JavaScript是一种基于原型prototype的语言,所以每创建一个对象时(当然在JavaScript中函数也是一种对象),它都有一个原型prototype用于指向其继承的属性、方法。这样通过prototype继承的方法并不是对象自身的,所以在使用这些方法时,需要通过prototype一级一级查找来得到。这样你会发现通过this定义的属性或者方法是该对象自身拥有的,所以我们每次通过类创建一个新对象时,this指向的属性和方法都会得到相应的创建,而通过prototype继承的属性或者方法是每个对象通过prototype访问到,所以我们每次通过类创建一个新对象时这些属性和方法不会再次创建。(如图2-1所示)。”

“哦,对了,解析图中的constructor又是指的什么呀。”

“constructor是一个属性,当创建一个函数或者对象时都会为其创建一个原型对象prototype,在prototype对象中又会像函数中创建this一样创建一个constructor属性,那么constructor属性指向的就是拥有整个原型对象的函数或对象,例如在本例中Book prototype中的constructor属性指向的就是Book类对象。”

2.2.2 这些都是我的——属性与方法封装
“原来是这样,”小白似乎明白些,“面向对象思想在学校里也学过,说的就是对一些属性方法的隐藏与暴露,比如私有属性、私有方法、共有属性、共有方法、保护方法等等,那么JavaScript中也有这些么?”

“你能想到这些很好。说明你有一定面向对象的基础了。不过你说的这些在JavaScript中没有显性的存在,但是我们可以通过一些灵活的技巧来实现它。”小铭继续解释说,“面向对象思想你可以想象成一个人,比如一位明星为了在社会中保持一个良好形象,她就会将一些隐私隐藏在心里,然而对于这位明星,她的家人认识她,所以会了解一些关于她的事情。外界的人不认识她,即使外界人通过某种途径认识她也仅仅了解一些她暴露出来的事情,不会了解她的隐私。如果想了解更多关于她的事情怎么办?对,还可以通过她的家人来了解,但是这位明星自己内心深处的隐私是永远不会被别人知道的。”

“那么在JavaScript中又是如何实现的呢?”小白问。

“由于JavaScript的函数级作用域,声明在函数内部的变量以及方法在外界是访问不到的,通过此特性即可创建类的私有变量以及私有方法。然而在函数内部通过this创建的属性和方法,在类创建对象时,每个对象自身都拥有一份并且可以在外部访问到。因此通过this创建的属性可看作是对象共有属性和对象共有方法,而通过this创建的方法,不但可以访问这些对象的共有属性与共有方法,而且还能访问到类(创建时)或对象自身的私有属性和私有方法,由于这些方法权利比较大,所以我们又将它看作特权方法。在对象创建时通过使用这些特权方法我们可以初始化实例对象的一些属性,因此这些在创建对象时调用的特权方法还可以看作是类的构造器。如下面的例子。”

// 私有属性与私有方法,特权方法,对象公有属性和对象共有方法,构造器
var Book = function(id, name, price){
  //私有属性
  var num = 1;
  //私有方法
  function checkId(){
  };
  //特权方法
  this.getName = function(){};
  this.getPrice = function(){};
  this.setName = function(){};
  this.setPrice = function(){};
  //对象公有属性
  this.id = id;
  //对象公有方法
  this.copy = function(){};
  //构造器
  this.setName(name);
  this.setPrice(price);
};

小白心中暗喜:“原来是这样呀,通过JavaScript函数级作用域的特征来实现在函数内部创建外界就访问不到的私有化变量和私有化方法。通过new关键字实例化对象时,由于对类执行一次,所以类的内部this上定义的属性和方法自然就可以复制到新创建的对象上,成为对象公有化的属性与方法,而其中的一些方法能访问到类的私有属性和方法,就像例子中家人对明星了解得比外界多,因此比外界权利大,因而得名特权方法。而我们在通过new关键字实例化对象时,执行了一遍类的函数,所以里面通过调用特权方法自然就可以初始化对象的一些属性了。可是在类的外部通过点语法定义的属性和方法以及在外部通过prototype定义的属性和方法又有什么作用呢?”

“通过new关键字创建新对象时,由于类外面通过点语法添加的属性和方法没有执行到,所以新创建的对象中无法获取他们,但是可以通过类来使用。因此在类外面通过点语法定义的属性以及方法被称为类的静态共有属性和类的静态共有方法。而类通过prototype创建的属性或者方法在类实例的对象中是可以通过this访问到的(如图2.1新创建的对象的proto指向了类的原型所指向的对象),所以我们将prototype对象中的属性和方法称为共有属性和共有方法,如:”

//类静态公有属性(对象不能访问)  
Book.isChinese = true;
//类静态公有方法(对象不能访问)
Book.resetTime = function(){
  console.log('new Tiem')
};
Book.prototype = {
  //公有属性
  isJSBook : false,
  //公有方法
  display : function(){}
}

“通过new关键字创建的对象实质是对新对象this的不断赋值,并将prototype指向类的prototype所指向的对象,而类的构造函数外面通过点语法定义的属性方法是不会添加到新创建的对象上去的。因此要想在新创建的对象中使用isChinese就得通过Book类使用而不能通过this,如Book.isChinese,而类的原型prototype上定义的属性在新对象里就可以直接使用,这是因为新对象的prototype和类的prototype指向的是同一个对象。”

于是小白半信半疑地写下了测试代码:

var b = new Book(11,'JavaScript设计模式',50);
console.log(b.num);    // undefined
console.log(b.isJSBook);  // false
console.log(b.id);      // 11
console.log(b.isChinese);  // undefined

“真的是这样,类的私有属性num以及静态共有属性isChinese在新创建的b对象里是访问不到的。而类的共有属性isJSBook在b对象中却可以通过点语法访问到。”

“但是类的静态公有属性isChinese可以通过类的自身访问。”

console.log(Book.isChinese);  // true
Book.resetTime();          // new Tiem

2.2.3 你们看不到我——闭包实现
“有时我们经常将类的静态变量通过闭包来实现。”

// 利用闭包实现
var Book = (function() {
 //静态私有变量
 var bookNum = 0;
 //静态私有方法
 function checkBook(name) {
 } 
 //返回构造函数
 return function(newId, newName, newPrice) {
  //私有变量
  var name, price;
  //私有方法
  function checkID(id){}
  //特权方法
  this.getName = function(){};
  this.getPrice = function(){};
  this.setName = function(){};
  this.setPrice = function(){};
  //公有属性
  this.id = newId;
  //公有方法
  this.copy = function(){};
  bookNum++
  if(bookNum > 100)
    throw new Error('我们仅出版100本书.');
  //构造器
  this.setName(name);
  this.setPrice(price);
 }
})();
Book.prototype = {
  //静态公有属性
  isJSBook : false,
  //静态公有方法
  display : function(){}
};

“小白,你知道闭包么?”

“不太了解。你能说说么?”

“闭包是有权访问另外一个函数作用域中变量的函数,即在一个函数内部创建另外一个函数。我们将这个闭包作为创建对象的构造函数,这样它既是闭包又是可实例对象的函数,即可访问到类函数作用域中的变量,如bookNum这个变量,此时这个变量叫静态私有变量,并且checkBook()可称之为静态私有方法。当然闭包内部也有其自身的私有变量以及私有方法如price,checkID()。但是,在闭包外部添加原型属性和方法看上去像似脱离了闭包这个类,所以有时候在闭包内部实现一个完整的类然后将其返回,看下面的例子。”

// 利用闭包实现
var Book = (function() {
  //静态私有变量
  var bookNum = 0;
  //静态私有方法
  function checkBook(name) {}
  //创建类
  function _book(newId, newName, newPrice) {
    //私有变量
    var name, price;
    //私有方法
    function checkID(id){}
    //特权方法
    this.getName = function(){};
    this.getPrice = function(){};
    this.setName = function(){};
    this.setPrice = function(){};
    //公有属性
    this.id = newId;
    //公有方法
    this.copy = function(){};
    bookNum++
    if(bookNum > 100)
      throw new Error('我们仅出版100本书.');
    //构造器
    this.setName(name);
    this.setPrice(price);
  }
  //构建原型
  _book.prototype = {
    //静态公有属性
    isJSBook : false,
    //静态公有方法
    display : function(){}
  };
  //返回类
  return _book;
})();

“哦,这样看上去更像一个整体。”

2.2.4 找位检察长——创建对象的安全模式
“对于你们初学者来说,在创建对象上由于不适应这种写法,所以经常容易忘记使用new而犯错误。”

“可是对于我们来说,这种错误发生也是不可避免的,毕竟不像你们工作了这么多年。但是你有什么好办法么?”

“哈哈,那是当然,如果你们犯错误有人实时监测不就解决了么,所以赶快找一位检察长吧。比如JavaScript在创建对象时有一种安全模式就完全可以解决你们这类问题。”

// 图书类
var Book = function(title, time, type){
  this.title = title;
  this.time = time;
  this.type = type;
}
// 实例化一本书
var book = Book('JavaScript', '2014', 'js');

“小白,你猜book这个变量是个什么?”

“Book类的一个实例吧。”为了验证自己的想法,小白写下测试代码。

console.log(book); // undefined
“怎么会是这样?为什么是一个undefined(未定义)?”小白不解。

“别着急,你来看看我的测试代码。”

console.log(window.title);  // JavaScript
console.log(window.time);   // 2014
console.log(window.type);   // js

“怎么样发现问题了么”,小铭问道。

“明明创建了一个Book对象,并且添加了title、time、type3个属性,怎么会添加到window上面去了,而且book这个变量还是undefined。”小白又看了看实例中的代码恍然大悟,“哦,原来是忘记了用new关键字来实例化了,可是为什么会出现这个结果呢?”

“别着急,首先你要明白一点,new关键字的作用可以看作是对当前对象的this不停地赋值,然而例子中没有用new,所以就会直接执行这个函数,而这个函数在全局作用域中执行了,所以在全局作用域中this指向的当前对象自然就是全局变量,在你的页面里全局变量就是window了,所以添加的属性自然就会被添加到window上面了,而我们这个book变量最终的作用是要得到Book这个类(函数)的执行结果,由于函数中没有return语句,这个Book类自然不会告诉book变量的执行结果了,所以就是undefined(未定义)。”

“原来是这样,看来创建时真是不小心呀,可是该如何避免呢?”小白感叹道。

“‘去找位检察长’呀,哈哈,使用安全模式吧。”

// 图书安全类
var Book = function(title, time, type){
  // 判断执行过程中this是否是当前这个对象(如果是说明是用new创建的)
  if(this instanceof Book){
    this.title = title;
    this.time = time;
    this.type = type;
  // 否则重新创建这个对象
  }else{
    return new Book(title, time, type);
  }
}
var book = Book('JavaScript', '2014', 'js');

“好了小白,测试一下吧。”

console.log(book);       // Book
console.log(book.title);    // JavaScript
console.log(book.time);     // 2014
console.log(book.type);     // js
console.log(window.title);   // undefined
console.log(window.time);    // undefined
console.log(window.type);    // undefined

“真的是这样呀,太好了,再也不用担心创建对象忘记使用new关键字的问题了。”

“好了说了很多,你也休息一下,好好回顾一下,后面还有个更重要的面向对象等着你——继承,这可是许多设计模式设计的灵魂。”

时间: 2024-09-08 20:20:00

《JavaScript设计模式》——2.2 包装明星——封装的相关文章

Javascript设计模式学习(一)封装和信息隐藏

在我们编程的过程中,我们应该尽可能的把数据和函数处理信息隐藏在对象内部,在Javascript中,我们怎样来做呢? 虽然Javascript中没有其他面向对象语言的声明共有和私有的关键字,但是我们仍可以通过一些手段来达到这样的效果.我们可以这样理解封装和信息隐藏,信息隐藏是我们的目标,因为我们不想太多无关的信息暴露在对象之外,而封装就是实现我们目标的方法.封装就可以被定义为在对象内部隐藏数据表达和具体实现的细节. 下面来学习下怎么用Javascript来实现接口:   第一种是:Fully Ex

学习JavaScript设计模式(封装)_javascript技巧

在JavaScript 中,并没有对抽象类和接口的支持.JavaScript 本身也是一门弱类型语言.在封装类型方面,JavaScript 没有能力,也没有必要做得更多.对于JavaScript 的设计模式实现来说,不区分类型是一种失色,也可以说是一种解脱. 从设计模式的角度出发,封装在更重要的层面体现为封装变化. 通过封装变化的方式,把系统中稳定不变的部分和容易变化的部分隔离开来,在系统的演变过程中,我们只需要替换那些容易变化的部分,如果这些部分是已经封装好的,替换起来也相对容易.这可以最大程

javascript设计模式 封装和信息隐藏(上)_javascript技巧

本文分上下两部分,上部讲基本模式(basic patterns):完全暴露法,下划线标记法和使用闭包:下部讲高级模式(Advanced Patterns),如何实现静态方法和属性,常量还有其他一些知识点. 封装是面向对象语言很基本也是很有用的特性,虽然javascript也可以称的上是面向对象语言,但他对封装的支持并不是很好,不像其他语言,只要使用private.protected就可以实现.但这并不是说就没有办法了,下面我就介绍下如何在javascript中实现封装. 一.基本模式(basic

常用的Javascript设计模式

<Practical Common Lisp>的作者 Peter Seibel 曾说,如果你需要一种模式,那一定是哪里出了问题.他所说的问题是指因为语言的天生缺陷,不得不去寻求和总结一种通用的解决方案. 不管是弱类型或强类型,静态或动态语言,命令式或说明式语言.每种语言都有天生的优缺点.一个牙买加运动员, 在短跑甚至拳击方面有一些优势,在练瑜伽上就欠缺一些. 术士和暗影牧师很容易成为一个出色的辅助,而一个背着梅肯满地图飞的敌法就会略显尴尬. 换到程序中, 静态语言里可能需要花很多功夫来实现装饰

javascript设计模式中的工厂模式示例

 这篇文章主要介绍了javascript设计模式中的工厂模式示例讲解,需要的朋友可以参考下 javaScript工厂方式原始的方式   因为对象的属性可以在对象创建后动态定义,这在 JavaScript 最初引入时都会编写类似下面的代码   代码如下: var oCar = new Object; oCar.color = "blue"; oCar.doors = 4; oCar.mpg = 25; oCar.showColor = function() {   alert(this.

《JavaScript设计模式》——2.3 传宗接代——继承

2.3 传宗接代--继承 "小白,看继承呢?"小铭忙完自己的事情走过来. "是呀,刚才学习类,发现每个类都有3个部分,第一部分是构造函数内的,这是供实例化对象复制用的,第二部分是构造函数外的,直接通过点语法添加的,这是供类使用的,实例化对象是访问不到的,第三部分是类的原型中的,实例化对象可以通过其原型链间接地访问到,也是为供所有实例化对象所共用的.然而在继承中所涉及的不仅仅是一个对象." "对呀,不过继承这种思想却很简单,如千年文明能够流传至今靠的就是传承

JavaScript设计模式初探_javascript技巧

目的:设计模式众多,尝试记录下学到的不同设计模式的优劣,方便以后查阅. 前言:半年前看高程的时候看到设计模式这章,云里雾里,不是看不明白,而是不明白为啥要如此麻烦只为创建一个对象.直到最近完成了自己第一个小项目,才体会到当代码量多起来时没有适当的规范与限制是多么大的灾难.于是重新翻开高程,总结下几种我学到的简单设计模式的优劣. 正文:本文一共介绍7种设计模式以及他们的应用场景.优劣. 1.工厂模式 直接用函数来封装对象,将对象作为返回值. function person (name,age) {

《JavaScript设计模式》——第9章 JavaScript设计模式9.1 Constructor(构造器)模式

第9章 JavaScript设计模式 在本章中,我们将探索一些经典与现代设计模式的JavaScript实现. 开发人员通常想知道他们是否应该在工作中使用一种"理想"的模式或模式集.这个问题没有明确的唯一答案,我们研究的每个脚本和 Web 应用程序可能都有它自己的个性化需求,我们需要思考模式的哪些方面能够为实现提供实际价值. 例如,一些项目可能会受益于观察者模式提供的解耦好处(这可以减少应用程序的某些部分对彼此的依赖度),而有些项目可能只是因为太小,而根本无需考虑解耦. 也就是说,一旦我

《JavaScript设计模式》——9.12 Decorator(装饰者)模式

9.12 Decorator(装饰者)模式 Decorator是一种结构型设计模式,旨在促进代码复用.与Mixin相类似,它们可以被认为是另一个可行的对象子类化的替代方案. 通常,Decorator提供了将行为动态添加至系统的现有类的能力.其想法是,装饰本身对于类原有的基本功能来说并不是必要的:否则,它就可以被合并到超类本身了. 装饰者可以用于修改现有的系统,希望在系统中为对象添加额外的功能,而不需要大量修改使用它们的底层代码.开发人员使用它们的一个共同原因是,应用程序可能包含需要大量不同类型对