封装
封装数据
在许多语言的对象系统中, 封装数据是由语法解析来实现的, 这些语言也许提供了 private publicprotected 等关键字来提供不同的访问权限例如: java
但在 js 里面, 并没有提供这些关键字的支持, 我们只能通过作用域来模拟实现封装性 (ES6 let 除外)
- 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 中遵循的规则
所有的数据都是对象
在 js 中, 除了 undefined 之外, 所有数据都是对象, numberbooleanstring 这几种基本类型数据也可以通过包装类的方式变成对象类型数据来处理那么根据原型规则, 这些对象一定有个根对象, 这个对象就是 Object.prototype,Object.prototype 对象是一个空的 对象我们在 JavaScript 遇到的每个对象, 实际上都是从 Object.prototype 对象克隆而来的, Object.prototype 对象就是它们的原型
- 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 克隆一个对象出来
接下来我们看看如何使用 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
实际上,__proto__就是对象跟对象构造器的原型联系起来的纽带 切记这句话, 对未来理解 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.prototype,A 的 prototype 指向自身的构造器, A.prototype = obj; 将引用指向了 obj, 所以在代码执行完后, 对象 a 的原型指向 obj, 虽然 a 本身没有 name 属性, 但原型上拥有 name 属性
总结
现在, 让我们来总结一下 js 创建对象的几种方式:
对象字面量
var obj = {};
通过对象构造器创建
- var Co = function(){};
- var obj = new Co();
通过 Object.create(); 创建 (ES5 以后版本支持)
- var Co = function(){};
- var obj = Object.create(Co);
通过 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
上面的几种创建对象的方式有本质的区别, 这里先不做详细说明, 后续学完作用域和闭包后再统一说明
来源: https://juejin.im/post/5a9c0cc3f265da2375065c94