前言
前些日子,在掘金上看到一片热门文章《在酷家乐做面试官的日子》。该文作者以面试官的角度,详细阐述了作为一名 web 应聘者应该具有哪些技能,才会更让人青睐。
在对比自身的过程中,发现有一些问题,或许了解,但不全面,这也是本系列文章诞生的缘由。
数据类型
最新的 ECMAScript 标准定义了 7 种数据类型:
6 种原始类型:
-
Boolean
-
Null
-
Undefined
-
Number
-
String
-
Symbol (ECMAScript 6 新定义)
和
- Object
原始类型
- 原型类型都存储在栈内存中,它是大小固定的并且方便于查找。
如下图,栈内存遵循先进后出的原则,优先声明的变量会存储在栈内存的底部。
- 除 Object 以外的所有类型都是不可变的(值本身无法被改变)
var msg = 'hello world'
// JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变
msg.toUpperCase() => HELLO WORLD
// msg 不会被改变
console.log(msg) => hello world
- 原始类型的比较( == / === )
var a = '1'
var b = 1
var c = true
/*
* '==' 是对值的比较
* '===' 是对值与数据类型的比较
*/
if(a == b && a == c){
console.log('== 是相等的')
} else {
console.log('== 是不相等的')
}
if(a === b || a === c){
console.log('=== 是相等的')
} else {
console.log('=== 是不相等的')
}
=> == 是相等的
=> === 是不相等的
Boolean
Boolean 表示一个逻辑实体,可以有两个值:true 和 false。
Null
Null 类型只有一个值: null。
Undefined
变量初始化时,没有进行赋值,它的值就是 undefined
Number
基于 IEEE 754 标准的双精度 64 位二进制格式的值(-(263 -1) 到 263 -1)
String
字符串、文本数据
Symbol
Symbol 可以创建一个独一无二的值,简单来说可以理解为一个 UUID
Object
除开原始类型,其它类型都是 Object 类型,包括
- Object
- Function
- Array
- Date
- RegExp
Object 类型都是复杂可变的,这些数据类型都有一个共同特点:“它们的值通过引用访问”。
如下图,变量 m 的数据类型是 Object,栈内存只是储存了一个 key 值,这个 key 值指向了堆内存的实际值。
当我们通过 m 赋值了一个 n,实际上 m、n 指向同一个内存空间。
引用类型与值类型
原始类型实际上就是值类型
Object 类型实际上就是引用类型
值类型
值类型是存储在栈内存空间,当我们对字符串进行操作,实际上值本身不会被改变。
/*
* 在 hello 函数中, 即时对 msg 做了操作,依然不会改变 msg 原始的值。
*/
var msg = 'hello word'
function hello(msg){
msg += '!'
return msg
}
console.log(hello(msg)) => hello world!
console.log(msg) => hello world
引用类型
引用类型的实际值是存储在堆内存空间,仅仅是通过一个栈内存的指针指向实际地址。当我们改变实际值的时候,它本身也会发生改变。
/*
* 在 hello 函数中, 对 msg 做了操作,可以发现 msg 本身的值也改变了。
*/
var msg = ['hello', 'world']
function hello(msg){
msg.push('!')
return msg
}
console.log(hello(msg)) => ["hello", "world", "!"]
console.log(msg) => ["hello", "world", "!"]
浅拷贝与深拷贝
在实际应用中,参数值被改变可能并不是我们想要的,这个时候就需要用到浅拷贝和深拷贝
- 浅拷贝
浅拷贝是拷贝顶级对象,不包括子对象
- 使用 Object.assgin 实现
- 使用 Array.concat 实现
- 使用 JSON.parse(JSON.stringify())
- ......
但是浅拷贝存在的一个问题就是,当为复杂对象时(存在嵌套子对象),改变子对象时,依然会对原始数据造成影响。
var obj = {
a: 1,
b: {
c: 2
}
}
var objClone = Object.assign({}, obj)
function f1(o){
o.a = 2
return obj
}
function f2(o){
o.b.c = 3
return obj
}
// 经过浅拷贝后,修改顶级对象时,源数据并未被更改
f1(objClone)
console.log(obj.a) => 1
// 经过浅拷贝后,修改嵌套对象时,源数据也被改变
f2(objClone)
console.log(obj.b.c) => 3
- 深拷贝
深拷贝是拷贝所有对象,包括子对象,优点是可以克隆子对象,缺点是存在遍历和类型判断,有执行速度和内存占用的风险。
直接拖上 jquery.extend 的源码,供大家参考:
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[ 0 ] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
// Skip the boolean and the target
target = arguments[ i ] || {};
i++;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !isFunction( target ) ) {
target = {};
}
// Extend jQuery itself if only one argument is passed
if ( i === length ) {
target = this;
i--;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( ( options = arguments[ i ] ) != null ) {
// Extend the base object
for ( name in options ) {
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
( copyIsArray = Array.isArray( copy ) ) ) ) {
src = target[ name ];
// Ensure proper type for the source value
if ( copyIsArray && !Array.isArray( src ) ) {
clone = [];
} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
clone = {};
} else {
clone = src;
}
copyIsArray = false;
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
}