javascript设计模式与开发实践(二)- 封装和原型模式

封装

封装数据

在许多语言的对象系统中,封装数据是由语法解析来实现的,这些语言也许提供了 private、
public、protected 等关键字来提供不一样的访问权限。例如:javahtml

但在js里面,并无提供这些关键字的支持,咱们只能经过做用域来模拟实现封装性。(ES6 let除外)java

var myTest = (function() {
    var _name ='jason';
    return {
        getName: function(){
            return _name;
        }
    }
})()
console.log(myTest.getName()); // 输出:jason
console.log(_name); // 输出:_name is not defined

这里,咱们定义了一个自执行函数(什么叫自执行函数?参考这篇文章),函数内部定义了一个变量_name,函数return一个对象出去(也叫模块模式),向外部暴露了一个getName函数,这里的_name变量就被封装在了myTest函数内部。编程

上文说的是封装数据,在js里,不光数据能够封装,还能够封装实现、封装类型,封装变化等,封装使得对象之间的耦合变得松散,对象之间只经过暴露的 API 接口来通讯。设计模式

原型模式和基于原型继承的JavaScript对象系统

在java中,对象必须由类建立而来,而在js中,对象倒是经过原型继承的方式得来的,在原型编程的思想中,类并非必需的,对象未必须要从类中建立而来, 一个对象是经过克隆另一个对象所获得的。浏览器

原型模式不单是一种设计模式,也被称为一种编程泛型。闭包

使用克隆的原型模式

从设计模式的角度讲,原型模式是用于建立对象的一种模式,若是咱们想要建立一个对象, 一种方法是先指定它的类型,而后经过类来建立这个对象。原型模式选择了另一种方式,咱们 再也不关心对象的具体类型,而是找到一个对象,而后经过克隆来建立一个如出一辙的对象。
原型模式的实现关键,是语言自己是否提供了 clone 方法。ECMAScript 5 提供了 Object.create 方法,能够用来克隆对象。代码以下:函数

var Plane = function(){ 
    this.blood = 100;
    this.attackLevel = 1;
    this.defenseLevel = 1;
};
var plane = new Plane();
plane.blood = 500;
plane.attackLevel = 10;
plane.defenseLevel = 7;
var clonePlane = Object.create( plane );
console.log( clonePlane ); // 输出:Object {blood: 500, attackLevel: 10,defenseLevel: 7}

在不支持 Object.create 方法的浏览器中,则可使用如下代码:

Object.create = Object.create || function( obj ){
    var F = function(){};
    F.prototype = obj;
    return new F(); 
}

原型编程范型的一些规则

  • 全部的数据都是对象。
  • 要获得一个对象,不是经过实例化类,而是找到一个对象做为原型并克隆它。
  • 对象会记住它的原型。
  • 若是对象没法响应某个请求,它会把这个请求委托给它本身的原型。

JavaScript中的原型继承

这里咱们来根据上面的范式来整理一下js中遵循的规则this

全部的数据都是对象

在js中,除了undefined以外,全部数据都是对象,number、boolean、string 这几种基本类型数据也能够经过“包装类”的方式变成对象类型数据来处理。那么根据原型规则,这些对象必定有个根对象,这个对象就是Object.prototype,Object.prototype 对象是一个空的 对象。咱们在 JavaScript 遇到的每一个对象,实际上都是从 Object.prototype 对象克隆而来的, Object.prototype 对象就是它们的原型。spa

var obj1 = new Object();
var obj2 = {};

console.log( Object.getPrototypeOf( obj1 ) === Object.prototype ); // 输出:true
console.log( Object.getPrototypeOf( obj2 ) === Object.prototype ); // 输出:true
要获得一个对象,不是经过实例化类,而是找到一个对象做为原型并克隆它

js中克隆是引擎内部实现的,咱们不用去关心他是如何实现的,咱们只要知道使用var obj1 = new Object()或者var obj2 = {},引擎就会从Object.prototype克隆一个对象出来。prototype

接下来咱们看看如何使用new运算符获得一个对象

var Person = function(name) {
    this.name = name;
    this.getName = function() {
        return this.name;
    }
}

var p = new Person('jason');

console.log(p.name);
console.log(p.getName());
console.log(Object.getPrototypeOf(p) === Person.prototype); // 输出true

在 JavaScript 中没有类的概念,这句话咱们已经重复过不少次了。但刚才不是明明调用了newPerson()吗?

*在这里 Person 并非类,而是函数构造器,JavaScript 的函数既能够做为普通函数被调用, 7 也能够做为构造器被调用。当使用 new 运算符来调用函数时,此时的函数就是一个构造器。 用
new 运算符来建立对象的过程,实际上也只是先克隆 Object.prototype 对象,再进行一些其余额 外操做的过程。*

对象会记住它的原型

JavaScript 给对象提供了一个名为__proto__的隐藏属性,某个对象的__proto__属性默认会指 向它的构造器的原型对象,即{Constructor}.prototype。
咱们经过代码来验证:

var objA = {
    name: 'jason'
}

console.log(objA.__proto__ === Object.prototype); //true

再来

var objB = function(age) {
    this.age = age;
}

var b = new objB();

console.log(b.__proto__ === objB.prototype); //true

<span style="color:red">实际上,__proto__就是对象跟“对象构造器的原型”联系起来的纽带</span> 切记这句话,对将来理解js原型链颇有帮助。

若是对象没法响应某个请求,它会把这个请求委托给它的构造器的原型

虽然 JavaScript 的对象最初都是由 Object.prototype 对象克隆而来的,但对象构造器的原型并不只限于 Object.prototype 上,能够动态指向其余对象。

var obj = { name: 'sven' };
var A = function(){};

A.prototype = obj;
var a = new A();
console.log(a.__proto__ === obj); //true
console.log(a.name); // 输出:sven

上面的代码中,第一行和第二行本没有任何关联,obj是个对象字面量建立的对象,A是个空方法,在第三行代码执行以前,obj__proto__指向Object.prototypeAprototype指向自身的构造器,A.prototype = obj;将引用指向了obj,因此在代码执行完后,对象a的原型指向obj,虽然a自己没有name属性,但原型上拥有name属性

总结

如今,让咱们来总结一下js建立对象的几种方式:

  1. 对象字面量
var obj = {};
  1. 经过对象构造器建立
var Co = function(){};
var obj = new Co();
  1. 经过Object.create();建立(ES5之后版本支持)
var Co = function(){};
var obj = Object.create(Co);
  1. 经过class建立(ES6之后版本支持)
class An{
    constructor(name){
        this.name = name;
    }
    
    getName() {
        return this.name;
    }
}

class Dog extends Animal { 
    constructor(name) {
        super(name); 
    }
    speak() {
        return "woof";
    } 
}

var dog = new Dog("Scamp");
console.log(dog.getName()); // Scamp
console.log(dog.speak()); // woof

上面的几种建立对象的方式有本质的区别,这里先不作详细说明,后续学完做用域和闭包后再统一说明。

相关文章
相关标签/搜索