一天一个知识点 - 浅谈 JavaScript 的数据类型

383 阅读4分钟

前言

前些日子,在掘金上看到一片热门文章《在酷家乐做面试官的日子》。该文作者以面试官的角度,详细阐述了作为一名 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", "!"]

浅拷贝与深拷贝

在实际应用中,参数值被改变可能并不是我们想要的,这个时候就需要用到浅拷贝和深拷贝

  • 浅拷贝

浅拷贝是拷贝顶级对象,不包括子对象

  1. 使用 Object.assgin 实现
  2. 使用 Array.concat 实现
  3. 使用 JSON.parse(JSON.stringify())
  4. ......

但是浅拷贝存在的一个问题就是,当为复杂对象时(存在嵌套子对象),改变子对象时,依然会对原始数据造成影响。


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;
}

参考

系列文章