浅析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一句话全选/取消全选
Mar 01 Javascript
WordPress中鼠标悬停显示和隐藏评论及引用按钮的实现
Jan 12 Javascript
JS ES6多行字符串与连接字符串的表示方法
Apr 26 Javascript
JavaScript基于activexobject连接远程数据库SQL Server 2014的方法
Jul 12 Javascript
react-native-fs实现文件下载、文本存储的示例代码
Sep 22 Javascript
vue-cli 自定义指令directive 添加验证滑块示例
Oct 19 Javascript
Vue使用lodop实现打印小结
Jul 06 Javascript
解析原来浏览器原生支持JS Base64编码解码
Aug 12 Javascript
使用Angular9和TypeScript开发RPG游戏的方法
Mar 25 Javascript
如何配置vue.config.js 处理static文件夹下的静态文件
Jun 19 Javascript
Vue中watch、computed、updated三者的区别及用法
Jul 27 Javascript
vue实现单一筛选、删除筛选条件
Oct 26 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中的时间显示
2007/01/18 PHP
10款实用的PHP开源工具
2015/10/23 PHP
使两个iframe的高度与内容自适应,且相等
2006/11/20 Javascript
JavaScript之IE的fireEvent方法详细解析
2013/11/20 Javascript
超链接的禁用属性Disabled使用示例
2014/07/31 Javascript
js通过location.search来获取页面传来的参数
2014/09/11 Javascript
js实现正则匹配中文标点符号的方法
2015/12/23 Javascript
jQuery+css实现的时钟效果(兼容各浏览器)
2016/01/27 Javascript
很棒的js选项卡切换效果
2016/07/15 Javascript
微信小程序仿RadioGroup改变样式的处理方案
2018/07/13 Javascript
解决layui checkbox 提交多个值的问题
2019/09/02 Javascript
jQuery 选择器用法基础入门示例
2020/01/04 jQuery
js实现简单的秒表
2020/01/16 Javascript
ElementUI 修改默认样式的几种办法(小结)
2020/07/29 Javascript
[54:19]完美世界DOTA2联赛PWL S2 Magma vs PXG 第二场 11.28
2020/12/01 DOTA
python uuid模块使用实例
2015/04/08 Python
python爬虫 正则表达式使用技巧及爬取个人博客的实例讲解
2017/10/20 Python
python实现人脸识别经典算法(一) 特征脸法
2018/03/13 Python
python读写csv文件方法详细总结
2019/07/05 Python
Python中的xlrd模块使用原理解析
2020/05/21 Python
解决Pytorch自定义层出现多Variable共享内存错误问题
2020/06/28 Python
基于python tkinter的点名小程序功能的实例代码
2020/08/22 Python
python tkinter实现下载进度条及抖音视频去水印原理
2021/02/07 Python
美国网上花店:JustFlowers
2017/02/12 全球购物
特步官方商城:Xtep
2017/03/21 全球购物
Booking.com亚太地区:Booking.com APAC
2020/02/07 全球购物
美国购物网站:Clickhere2shop
2021/01/28 全球购物
房地产销售经理岗位职责
2014/01/01 职场文书
寒假实习自荐信
2014/01/26 职场文书
产品发布会策划方案
2014/05/12 职场文书
社区扶贫帮困工作总结
2015/05/20 职场文书
2015年社区党建工作汇报材料
2015/06/25 职场文书
PyTorch梯度裁剪避免训练loss nan的操作
2021/05/24 Python
对Keras自带Loss Function的深入研究
2021/05/25 Python
SQL试题 使用窗口函数选出连续3天登录的用户
2022/04/24 Oracle
MySQL选择合适的备份策略和备份工具
2022/06/01 MySQL