浅析JavaScript基本类型与引用类型


Posted in Javascript onMay 28, 2014

对于 JavaScript 类型,可以简单地概括为:相对于强类型语言来说,它是弱(松散)类型的语言;有基本类型和引用类型,他们是区别是一个有固定空间存在于栈内存中,一个没有固定空间保存在堆内存中并且在栈内存中保存了一个指向实现位置的指针。

市面上很多书都有不小的篇幅在讲。这篇文章会讲几个方面,这些方面可能会需要你对 JavaScript 已经有了一些简单的了解,特别是 JavaScript 的类型。如果还不一解,可以随手拿起一本关于 JavaScript 的书翻翻,再来看本文。

一、基本类型与引用类型

1.基本类型:Undefined / Null / Boolean / Number / String
2.引用类型:Object / Array / Function / Date / RegExp / Error / Map / Set …

为什么引用类型没有枚举完呢,因为这里面你了解这么多就够了,至少在我讲的这篇中这些已经足够。其他的可能很少会用到,甚至像 Map 、Set 这样的也不是所有浏览器都支持。

二、JavaScript 类型的判断

在 JavaScript 有两个 operator 可以用以判断类型。他们是 typeof 和 instanceof,不过圈子很小,它们混的可不是那么好,是出了名的不靠谱。少数情况也是对的,很多情况下是不靠谱的。看看就知道了:

// 靠谱的时候:
typeof 'sofish' // string
new String('sofish') instanceof String // true
// 不靠谱的时候:
typeof [] // object
typeof null // object 
'sofish' instanceof String // false

呃~ 可能很多初学的 JavaScript 程序员会因此爆粗口。还大部分人在需要用 JS 的时候已经有了 jQuery 等这样的库,他们都做了封装,让你可以方便地检测类型。当然,事实上要检测也不麻烦,因为那句「在 JavaScript 中,一切都是对象」,当然像很多文档中说到的,undefined 其实和 NaN, Infinity 都只是一个全局属性。你大概知道就可以了。但「对象」可以帮到我们:

/* 检测对象类型
 * @param: obj {JavaScript Object}
 * @param: type {String} 以大写开头的 JS 类型名
 * @return: {Boolean}
 */
function is(obj, type)  {
  return Object.prototype.toString.call(obj).slice(8, -1) === type;
}

这样的话,我们就可以利用 is 这个函数来帮我们搞定类型判断了,并且这个简单的函数有很好的兼容性,可以用到你的项目中去。情况如:

is('sofish', 'String') // true
is(null, 'Null') // true
is(new Set(), 'Set') // true

三、JavaScript 类型的转换

在 JavaScript 中,变量(属性)的类型是可以改变的。最常看到的是 String 与 Number 之间的转换。如何把 1 + '2' 变成 12 呢?这里面有必要理解一下 + 号 operator,它是一个数学运算符,同时也是 JavaScript 中的字符串连字符。所以新手会经常会看到一个有趣的现象,当使用 + 号的时候有时计算出来的不是想要的,而用 - 号却总能得到「正确」的答案。

1 + '2' // '12'
1 + (+'2') // 3
1 - '2' // -1

这里面其实就是因为 + 的双重角色导致的。在上面的代码中,可以注意到第二条表达式在 String 前面运用了一个 + 号,强制把它的类转换为 Number。而对于 JavaScript 的类型转换理解,大多数情况下,只要理解 + 具有双重角色就可以了。其他的可以理解类,类似都是可以用赋值/重载来修改的,甚至包括 Error:
var err = new Error();
console.log(err instanceof Error); // true
err = 'sofish';
console.log(err); // 'sofish'

四、JavaScript 引用类型

这一点是本文的一个难点。相于基本类型,引用可以为其添加属性和方法;引用类似的值是一个引用,把一个引用类型的值赋给一个变量,他们所指向的是同一存储在堆内存中的值。变量(属性)可以重载,但复制会是一件很有趣的事情,后面我们会详细来说。

1. 添加属性和方法

下面的代码我们将会看到,假设我们对一个基本类似赋值,它并不会报错,但在获取的时候却是失效的:

var arr = [1,2,3];
arr.hello = 'world';
console.log(arr.hello); // 'world'
var str = 'sofish';
str.hello = 'world';
console.log(str.hello); // undefined

2. 引用类型值的操作

由于引用类型存储在栈内存中的是一个引用,那么当我们指向的同一个原始的值,对值的操作将会影响所有引用;这里有一个例是,重新赋值(并非对值的直接操作)会重新创建一个对象,并不会改变原始值。比如:

var arr = [1,2,3], sofish = arr;
sofish.push('hello world');
console.log(arr); // [1, 2, 3, 'hello world']
// 非相同类型
sofish = ['not a fish']; // 当 sofish 类似改变时,不会改变原始值
console.log(arr);// [1, 2, 3, 'hello world']

3. 引用类型值的复制

对原始值的操作会影响所有引用,而这不一定是我们想要的,有时候我们需要复制一个全新的对象,操作的时候不影响其他引用。而一般情况也,像 Date / Function / RegExp … 都很少有具体的操作,主要是像 Array 和 Object 会有添加项、属性等操作。所以我们主要需要理解的是如何复制 Array 和 Object 对象。

3.1 数组的复制

在 Array 对象中,存在 slice 方法返回一个截取的数组,在 ES5 中 filter 等也返回一个新的数组,那么我们可能利用这个方法来进行复制。

var arr = [1, 2, 3];
var sofish = arr.slice();
// 对新的数组进行操作并不会影响到原始数组
sofish.push('hello world');
console.log(arr); // [1, 2, 3]

3.2 对象的复制

在 Array 的复制中我们使用的是 slice 方法,实际上对于 Array 和 Object 中都可以利用 for ... in 循环来进行遍历并赋值来进行复制。

var obj = { name: 'sofish' }, sofish = {}, p;
for (p in obj) sofish[p] = obj[p];
// 对新的对象操作并不会影响原始值
sofish.say = function() {};
console.log(obj); // { name: 'sofish' }

3.3 Shadow / Deep Copy

像上面的操作,就是我们常说的浅拷贝(Shadow Copy)。不过在 Array 和 Object 都可以有多层(维),像这样的拷贝只考虑到最上面一层的值,在可能存在的值中的 Array 和 Object 都还是指向了原始对象。比如:

var arr = [1, { bio: 'not a fish' } ], sofish = [], p;
for(p in arr) {
  sofish[p] = arr[p];
}
// 对 `sofish` 中包含的对象 `cat` 的操作会影响原始值
sofish[1].bio = 'hackable';
console.log(arr);//  [1, cat: { bio: 'hackable' } ]

那么如何做呢?来一个 copy() 函数解决这个问题:
/* 复制对象
 * @param: obj {JavaScript Object} 原始对象
 * @param: isDeep {Boolean} 是否为深拷贝
 * @return: {JavaScript Object} 返回一个新的对象
 */
function copy(obj, isDeep) {
  var ret = obj.slice ? [] : {}, p, prop;
  // 配合 is 函数使用
  if(!isDeep && is(obj, 'Array')) return obj.slice();
  for(p in obj) {
    if(!obj.hasOwnProperty(p)) continue;
    prop = obj[p];
    ret[p] = (is(prop, 'Object') || is(prop, 'Array')) ? 
      copy(prop, isDeep) : prop;
  }
  return ret;
}

这样,我们就可以通过 copy(obj, isDeep) 函数来复制一个 Array 或者 Object 。可以测试一下:
var arr = [1, {bio: 'not a fish'}];
var sofish = copy(arr);
// 浅拷贝对于第一层的操作不影响原始值,但影响第二层
sofish.push('cat'); 
console.log(arr); //  [1, {bio: 'not a fish'}]
sofish[1].bio = 'hello world';
console.log(arr) //  [1, {bio: 'hello world'}]
// 深拷贝则不会影响原始值
sofish = copy(arr, 1);
sofish[1].bio = 'foo or bar';
console.log(arr); // [1, {bio: 'hello world'}]

到此。你基本上要了解的关于类型的比较难的点,应该是都基本了解了。当然,复制是最麻烦的一个点,除了经常需要操作的 Array 和 Object 来说,还有 Date / Function / RegExp 的复制。

Javascript 相关文章推荐
多个checkbox被选中时如何判断是否有自己想要的
Sep 22 Javascript
深入浅出分析javaScript中this用法
May 09 Javascript
基于jQuery和hwSlider实现内容左右滑动切换效果附源码下载(一)
Jun 22 Javascript
JavaScript数组去重的几种方法效率测试
Oct 23 Javascript
详解jQuery简单的表格应用
Dec 16 Javascript
js将字符串中的每一个单词的首字母变为大写其余均为小写
Jan 05 Javascript
微信小程序-拍照或选择图片并上传文件
Jan 06 Javascript
jquery.guide.js新版上线操作向导镂空提示jQuery插件(推荐)
May 20 jQuery
AngularJS获取json数据的方法详解
May 27 Javascript
AngularJs点击状态值改变背景色的实例
Dec 18 Javascript
JS中appendChild追加子节点无效的解决方法
Oct 14 Javascript
使用JS实现动态时钟
Mar 12 Javascript
最短的IE判断var ie=!-[1,]分析
May 28 #Javascript
jQuery 1.9使用$.support替代$.browser的使用方法
May 27 #Javascript
什么是cookie?js手动创建和存储cookie
May 27 #Javascript
js打开windows上的可执行文件示例
May 27 #Javascript
JavaScript数值数组排序示例分享
May 27 #Javascript
JavaScript作用域链示例分享
May 27 #Javascript
Node调试工具JSHint的安装及配置教程
May 27 #Javascript
You might like
PHP实现阳历到农历转换的类实例
2015/03/07 PHP
javascript 数组排序函数
2009/08/20 Javascript
一个简单的瀑布流效果(主体形式自写)
2013/05/27 Javascript
jQuery中mouseover事件用法实例
2014/12/26 Javascript
JS拖拽组件学习使用
2016/01/19 Javascript
JS动态计算移动端rem的解决方案
2016/10/14 Javascript
node.js入门学习之url模块
2017/02/25 Javascript
详解使用vue脚手架工具搭建vue-webpack项目
2017/05/10 Javascript
AngularJS ng-repeat指令及Ajax的应用实例分析
2017/07/06 Javascript
Node.JS 循环递归复制文件夹目录及其子文件夹下的所有文件
2017/09/18 Javascript
Javascript的this详解
2019/03/23 Javascript
js实现for循环跳过undefined值示例
2019/07/02 Javascript
JavaScript 反射和属性赋值实例解析
2019/10/28 Javascript
Vue实现开关按钮拖拽效果
2020/09/22 Javascript
Python深入学习之特殊方法与多范式
2014/08/31 Python
Python的函数的一些高阶特性
2015/04/27 Python
python读写ini配置文件方法实例分析
2015/06/30 Python
python实现人人自动回复、抢沙发功能
2018/06/08 Python
python xlsxwriter创建excel图表的方法
2018/06/11 Python
解决pycharm中opencv-python导入cv2后无法自动补全的问题(不用作任何文件上的修改)
2020/03/05 Python
python 实现简易的记事本
2020/11/30 Python
Hotter Shoes英国官网:英伦风格,舒适的鞋子
2017/12/28 全球购物
德国孕妇装和婴童服装网上商店:bellybutton
2018/04/12 全球购物
计算机本科生自荐信
2013/10/15 职场文书
2014年元旦活动方案
2014/02/15 职场文书
8和9的加减法教学反思
2014/05/01 职场文书
运动会广播稿200米(5篇)
2014/10/15 职场文书
2015年教师师德师风承诺书
2015/04/28 职场文书
舌尖上的中国观后感
2015/06/02 职场文书
婚礼双方父亲致辞
2015/07/27 职场文书
宣传委员竞选稿
2015/11/19 职场文书
《扇形统计图》教学反思
2016/02/17 职场文书
《平移和旋转》教学反思
2016/02/19 职场文书
Python 实现绘制子图及子图刻度的变换等问题
2021/05/31 Python
python实现简单聊天功能
2021/07/07 Python
德劲DE1107指针试高灵敏度全波段收音机机评
2022/04/05 无线电