笔者最近整理了一些前端技术文章,若是有兴趣能够参考这里:muwoo blogs。接下来咱们进入正片:前端
大多数状况下,咱们能够经过typeof
属性来判断。只不过有一些例外,好比:git
var fn = new Function ('a', 'b', 'return a + b') typeof fn // function
关于function
属不属于js的数据类型,这里也有相关的讨论JavaScript 里 Function 也算一种基本类型?github
咱们来看一下 MDN 中对基本数据类型的一些定义:json
除 Object 之外的全部类型都是不可变的(值自己没法被改变)。例如,与 C 语言不一样,JavaScript 中字符串是不可变的(译注:如,JavaScript 中对字符串的操做必定返回了一个新字符串,原始字符串并无被改变)。咱们称这些类型的值为“原始值”。
var a = 'string' a[0] = 'a' console.log(a) // string
咱们一般状况下都是对一个变量从新赋值,而不是改变基本数据类型的值。在 js 中是没有方法是能够改变布尔值和数字的。却是有不少操做字符串的方法,可是这些方法都是返回一个新的字符串,并无改变其原有的数据。好比:数组
引用类型(object)是存放在堆内存中的,变量其实是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每一个空间大小不同,要根据状况开进行特定的分配,例如。数据结构
var person1 = {name:'jozo'}; var person2 = {name:'xiaom'}; var person3 = {name:'xiaoq'};
引用类型的值是可变的:函数
person1['name'] = 'muwoo' console.log(person1) // {name: 'muwoo'}
了解了基本数据类型与引用类型的区别以后,咱们就应该能明白传值与传址的区别了。
在咱们进行赋值操做的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,而后再把再将值赋值到新的栈中。例如:post
var a = 10; var b = a; a ++ ; console.log(a); // 11 console.log(b); // 10
因此说,基本类型的赋值的两个变量是两个独立相互不影响的变量。spa
可是引用类型的赋值是传址。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,所以二者之间操做互相有影响。例如:指针
var a = {}; // a保存了一个空对象的实例 var b = a; // a和b都指向了这个空对象 a.name = 'jozo'; console.log(a.name); // 'jozo' console.log(b.name); // 'jozo' b.age = 22; console.log(b.age);// 22 console.log(a.age);// 22 console.log(a == b);// true
先来看一段代码的执行:
var obj = {a: 1, b: {c: 2}} var obj1 = obj var obj2 = shallowCopy(obj); function shallowCopy(src) { var dst = {}; for (var prop in src) { if (src.hasOwnProperty(prop)) { dst[prop] = src[prop]; } } return dst; } var obj3 = Object.assign({}, obj) obj.a = 2 obj.b.c = 3 console.log(obj) // {a: 2, b: {c: 3}} console.log(obj1) // {a: 2, b: {c: 3}} console.log(obj2) // {a: 1, b: {c: 3}} console.log(obj3) // {a: 1, b: {c: 3}}
这段代码能够说明赋值获得的对象 obj1
只是将指针改变,其引用的仍然是同一个对象,而浅拷贝获得的的 obj2
则是从新建立了新对象。可是,若是原对象obj
中存在另外一个对象,则不会对对象作另外一次拷贝,而是只复制其变量对象的地址。这是由于浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据。
对于数组,更长见的浅拷贝方法即是slice(0)
和 concat()
ES6 比较常见的浅拷贝方法即是 Object.assign
经过上面的这些说明,相信你对深拷贝大体了解了是怎样一个东西了:深拷贝是对对象以及对象的全部子对象进行拷贝。那么如何实现这样一个深拷贝呢?
对于常规的对象,咱们能够经过JSON.stringify
来说对象转成一个字符串,而后在用JSON.parse
来为其分配另外一个存储地址,这样能够解决内存地址指向同一个的问题:
var obj = {a: {b: 1}} var copy = JSON.parse(JSON.stringify(obj)) obj.a.b = 2 console.log(obj) // {a: {b: 2}} console.log(copy) // {a: {b: 1}}
可是 JSON.parse()
、JSON.stringify
也存在一个问题,JSON.parse()
和J SON.stringify()
能正确处理的对象只有Number、String、Array
等可以被 json 表示的数据结构,所以函数这种不能被 json 表示的类型将不能被正确处理。
var target = { a: 1, b: 2, hello: function() { console.log("Hello, world!"); } }; var copy = JSON.parse(JSON.stringify(target)); console.log(copy); // {a: 1, b: 2} console.log(JSON.stringify(target)); // "{"a":1,"b":2}"
既然浅拷贝只能实现非object
第一层属性的复制,那么遇到object
只须要经过递归实现浅拷贝其中内部的属性便可:
function extend (source) { var target if (typeof source === 'object') { target = Array.isArray(source) ? [] : {} for (var key in source) { if (source.hasOwnProperty(key)) { if (typeof source[key] !== 'object') { target[key] = source[key] } else { target[key] = extend(source[key]) } } } } else { target = source } return target } var obj1 = {a: {b: 1}} var cpObj1 = extend(obj1) obj1.a.b = 2 console.log(cpObj1) // {a: {b: 1}} var obj2 = [[1]] var cpObj2 = extend(obj2) obj2[0][0] = 2 console.log(cpObj2) // [[1]]
咱们再来看一下 Zepto
中深拷贝的代码:
// 内部方法:用户合并一个或多个对象到第一个对象 // 参数: // target 目标对象 对象都合并到target里 // source 合并对象 // deep 是否执行深度合并 function extend(target, source, deep) { for (key in source) if (deep && (isPlainObject(source[key]) || isArray(source[key]))) { // source[key] 是对象,而 target[key] 不是对象, 则 target[key] = {} 初始化一下,不然递归会出错的 if (isPlainObject(source[key]) && !isPlainObject(target[key])) target[key] = {} // source[key] 是数组,而 target[key] 不是数组,则 target[key] = [] 初始化一下,不然递归会出错的 if (isArray(source[key]) && !isArray(target[key])) target[key] = [] // 执行递归 extend(target[key], source[key], deep) } // 不知足以上条件,说明 source[key] 是通常的值类型,直接赋值给 target 就是了 else if (source[key] !== undefined) target[key] = source[key] }
内部实现其实也是差很少。