封装
最简单的封装:
假定我们把人看成一个对象,它有"名字"和"性别"两个属性(property),以它作为原型,通过字面量(对象直接量)表示如下:
var Person = {
name : '',
sex : ''
}
现在,我们需要根据这个原型对象的规格(schema),生成两个实例对象。
var man = {}; // 创建一个空对象
man.name = "亚当"; // 按照原型对象的属性赋值
man.sex = 1;
var women = {};
women.name = "夏娃";
women.sex = 0;
这就是最简单的封装了,把两个属性封装在一个对象里面。其中封装的是数据。
进阶封装-工厂模式:
前言:工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象(封装)了创建具体对象的过程。js中,用函数来封装以特定接口创建对象的细节。
代码:
function createPerson(name, sex) {
return {
name: name,
sex: sex
};
}
//或者 变体
function createPerson(name, sex) {
var o = new Object();
o.name = name;
o.sex = sex;
return o;
}
var adam = createPerson("亚当", 1);
var eve = createPerson("夏娃", 0);
构造函数式模式:
前言:JS中的构造函数可以用来创建特定类型的对象。也可以创建自定义的构造函数,从而自定义对象类型的属性和方法。
代码:
function Person(name, sex) {
this.name = name;
this.sex = sex;
this.sayName = function () {
alert(this.name);
};
}
var adam = new Person("亚当", 1);
var eve = new Person("夏娃", 0);
将上方工厂模式的代码更改为构造函数模式的代码,除了增加了sayName的方法外,还有些区别。
区别:
没有显式的创建对象;
直接将属性和方法赋值给了this对象;
没有return;
注意:要创建Person的实例,必须使用new操作符。
以这种方式调用构造函数实际上会经历如下4个步骤:
创建一个新对象;
将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
执行构造函数中的代码(为这个新对象添加属性);
返回新对象;
可以通过 instanceof 操作符确认实例是否后属于Person构造函数
代码:
adam instanceof Person;//true
eve instanceof Person;//true
这里稍微点一下构造函数
构造函数模式
为了解决从原型对象生成实例的问题,Javascript提供了一个构造函数(Constructor)模式。
所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。
比如,猫的原型对象现在可以这样写,
function Cat(name,color){
this.name=name;
this.color=color;
}
我们现在就可以生成实例对象了。
var cat1 = new Cat("大毛","黄色");
var cat2 = new Cat("二毛","黑色");
alert(cat1.name); // 大毛
alert(cat1.color); // 黄色
这时cat1和cat2会自动含有一个constructor属性,指向它们的构造函数。
alert(cat1.constructor == Cat); //true
alert(cat2.constructor == Cat); //true
Javascript还提供了一个instanceof运算符,验证原型对象与实例对象之间的关系。
alert(cat1 instanceof Cat); //true
alert(cat2 instanceof Cat); //true
认识深拷贝和浅拷贝
先来看一下下面的代码:
var a = 1;
var b = a;
b = 2;
console.log(a, b); // 1 2
var obj1 = {
a: 1
};
var obj2 = obj1;
obj2.a = 2;
console.log(obj1.a, obj2.a); // 2 2
我们知道,JavaScript中有两大数据类型:分别是值类型和对象类型。值类型是没有深拷贝和浅拷贝这个概念的,这个和它们存储方式有关。值类型的数据是存储在栈内存中,按值访问;而对象类型的数据是存储在堆内存中,按引用(地址、指针)访问。
对象类型的数据直接赋值后会共用一个引用,改变其中一个都会影响到另一个,深拷贝和浅拷贝就是为解决这类对象直接赋值后依然“连接”的问题。那么就可以回答第一个问题,什么是浅拷贝和深拷贝了。
浅拷贝:复制一层对象的属性,并不包括对象里面是引用类型的数据,当改变拷贝的对象里面的引用类型时,源对象也会改变。
深拷贝:重新开辟一个内存空间,需要递归拷贝对象里的引用,直到子属性都为基本类型。两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
第二个问题,它们的区别:首先深拷贝和浅拷贝只针对如Object,Array这样的复杂对象的。深拷贝和浅拷贝的“深浅”主要针对的是对象的“深度”,简单来说,浅拷贝只复制一层对象的属性,而深拷贝则递归复制了所有层级。
Object.assign()
Object.assign()方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
var obj1 = {
a: 1,
}
var obj2 = Object.assign({}, obj1);
obj2.a = 2;
console.log(obj1.a); // 1
- 简单手写实现
下面来简单的实现一下浅拷贝:
function copy(obj) {
var target = {};
for (key in obj) {
target[key] = obj[key];
}
return target;
}
var obj1 = {
a: 1,
b: {
c: '我就是我,颜色不一样的烟火!',
},
}
var obj2 = copy(obj1);
obj2.a = 2;
console.log(obj1.a); // 1
以上便是浅拷贝的简单实现,拷贝完成后更改obj2.a的值,但是obj1.a的值没有发生改变,说明实现了浅拷贝。
浅拷贝可以解决常见的问题,但是如果不常见呢,比如说对象里面还有子对象,那么用浅拷贝就不够彻底;比如:
var obj3 = copy(obj1);
obj3.b.c = '你被我改变了吧!哈哈';
console.log(obj1.b.c); // 你被我改变了吧!哈哈
上面代码中,拷贝完成后更改了obj3.b.c,结果obj1.b.c也随之改变,说明子对象b依然存在共用同一个引用的现象,所以浅拷贝拷贝的并不彻底。这时候深拷贝就该上场了,它能用递归的思想继续深挖,直到最底层为止。
递归拷贝实现
下面用递归简单的实现一下深拷贝:
function deepCopy(obj) {
var target = isArray(obj) ? [] : {};
for (key in obj) {
var copy = obj[key];
if (isObject(copy) || isArray(copy)) {
target[key] = deepCopy(copy); // 核心代码
} else {
target[key] = copy;
}
}
return target;
}
function isObject(obj) {
return toString.call(obj) === '[object Object]';
}
function isArray(arr) {
return toString.call(arr) === '[object Array]';
}
var obj4 = deepCopy(obj1);
obj4.b.c = '我摊牌了!';
console.log(obj1.b.c); // 我就是我,颜色不一样的烟火!
深拷贝是拷贝得非常彻底的,可以做到真正意义上的杜绝共用一个引用的问题。