浅析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 相关文章推荐
各种效果的jquery ui(接口)介绍
Sep 17 Javascript
JavaScript中的函数模式详解
Feb 11 Javascript
jquery渐隐渐显的图片幻灯闪烁切换实现方法
Feb 26 Javascript
jquery html动态添加的元素绑定事件详解
May 24 Javascript
图文详解Javascript中的上下文和作用域
Feb 15 Javascript
jQuery+PHP+Mysql实现抽奖程序
Apr 12 jQuery
JS按钮闪烁功能的实现代码
Jul 21 Javascript
JQuery 获取多个select标签option的text内容(实例)
Sep 07 jQuery
JS实现仿微信支付弹窗功能
Jun 25 Javascript
Vue组件系列开发之模态框
Apr 18 Javascript
Vue数据驱动表单渲染,轻松搞定form表单
Jul 19 Javascript
vue 翻页组件vue-flip-page效果
Feb 05 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读取MySQL数据代码
2008/06/05 PHP
php $_SERVER["REQUEST_URI"]获取值的通用解决方法
2010/06/21 PHP
让PHP COOKIE立即生效,不用刷新就可以使用
2011/03/09 PHP
使用PHP Socket 编程模拟Http post和get请求
2014/11/25 PHP
PHP Ajax实现无刷新附件上传
2016/08/17 PHP
jquery.ui.draggable中文文档
2009/11/24 Javascript
extjs 为某个事件设置拦截器
2010/01/15 Javascript
js弹窗返回值详解(window.open方式)
2014/01/11 Javascript
使用JavaScript 编写简单计算器
2014/11/24 Javascript
JavaScript中逗号运算符介绍及使用示例
2015/03/13 Javascript
jQuery实现自定义事件的方法
2015/04/17 Javascript
Javascript实现颜色rgb与16进制转换的方法
2015/04/18 Javascript
JavaScript每天定时更换皮肤样式的方法
2015/07/01 Javascript
js判断当前页面用什么浏览器打开的方法
2016/01/06 Javascript
原生javascript实现解析XML文档与字符串
2016/03/01 Javascript
基于zepto的移动端轻量级日期插件--date_picker
2016/03/04 Javascript
基于javascript实现tab切换特效
2016/03/29 Javascript
JS中JSON对象和String之间的互转及处理技巧
2016/04/06 Javascript
ionic由于使用了header和subheader导致被遮挡的问题的两种解决方法
2016/09/22 Javascript
Easyui使用Dialog行内按钮布局的实例
2017/07/27 Javascript
微信小程序之蓝牙的链接
2017/09/26 Javascript
bootstrap treeview 树形菜单带复选框及级联选择功能
2018/06/08 Javascript
JSON基本语法及与JavaScript的异同实例分析
2019/01/04 Javascript
ES6知识点整理之Proxy的应用实例详解
2019/04/16 Javascript
通过图带你深入了解vue的响应式原理
2019/06/21 Javascript
js模拟F11页面全屏显示
2019/09/17 Javascript
python logging 日志轮转文件不删除问题的解决方法
2016/08/02 Python
Python开发的实用计算器完整实例
2017/05/10 Python
对python 读取线的shp文件实例详解
2018/12/22 Python
python numpy中cumsum的用法详解
2019/10/17 Python
python实现秒杀商品的微信自动提醒功能(代码详解)
2020/04/27 Python
Python嵌入C/C++进行开发详解
2020/06/09 Python
跑操口号
2014/06/12 职场文书
毕业生工作求职信
2014/06/30 职场文书
《时代广场的蟋蟀》读后感:真挚友情,温暖世界!
2020/01/08 职场文书
Pandas加速代码之避免使用for循环
2021/05/30 Python