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

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

一、基本类型与引用类型

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

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

二、JavaScript 类型的判断

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

  1. // 靠谱的时候:
  2. typeof 'sofish' // string
  3. new String('sofish') instanceof String // true
  4.  
  5. // 不靠谱的时候:
  6. typeof [] // object
  7. typeof null // object
  8. 'sofish' instanceof String // false

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

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

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

  1. is('sofish', 'String') // true
  2. is(null, 'Null') // true
  3. is(new Set(), 'Set') // true

三、JavaScript 类型的转换

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

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

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

  1. var err = new Error();
  2. console.log(err instanceof Error); // true
  3.  
  4. err = 'sofish';
  5. console.log(err); // 'sofish'

四、JavaScript 引用类型

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

1. 添加属性和方法

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

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

2. 引用类型值的操作

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

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

3. 引用类型值的复制

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

3.1 数组的复制

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

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

3.2 对象的复制

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

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

3.3 Shadow / Deep Copy

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

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

那么如何做呢?来一个 copy() 函数解决这个问题:

  1. /* 复制对象
  2. * @param: obj {JavaScript Object} 原始对象
  3. * @param: isDeep {Boolean} 是否为深拷贝
  4. * @return: {JavaScript Object} 返回一个新的对象
  5. */
  6. function copy(obj, isDeep) {
  7. var ret = obj.slice ? [] : {}, p;
  8. // 配合 is 函数使用
  9. if(!isDeep && is(obj, 'Array')) return obj.slice();
  10. for(p in obj) {
  11. var prop = obj[p];
  12. if(!obj.hasOwnProperty(p)) continue;
  13. if(is(prop, 'Object') || is(prop, 'Array')) {
  14. ret[p] = copy(prop, isDeep);
  15. } else {
  16. ret[p] = prop;
  17. }
  18. }
  19. return ret;
  20. }

这样,我们就可以通过 copy(obj, isDeep) 函数来复制一个 Array 或者 Object 。可以测试一下:

  1. var arr = [1, {bio: 'not a fish'}];
  2. var sofish = copy(arr);
  3.  
  4. // 浅拷贝对于第一层的操作不影响原始值,但影响第二层
  5. sofish.push('cat');
  6. console.log(arr); // [1, {bio: 'not a fish'}]
  7. sofish[1].bio = 'hello world';
  8. console.log(arr) // [1, {bio: 'hello world'}]
  9.  
  10. // 深拷贝则不会影响原始值
  11. sofish = copy(arr, 1);
  12. sofish[1].bio = 'foo or bar';
  13. console.log(arr); // [1, {bio: 'hello world'}]

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

JavaScript 类型浅解的更多相关文章

  1. js课程 1-3 Javascript变量类型详解

    js课程 1-3  Javascript变量类型详解 一.总结 一句话总结:js对象点(属性方法),json对象冒号(属性方法).属性和方法区别只有一个括号. 1.json对象中的函数的使用? 函数名 ...

  2. JavaScript事件详解-jQuery的事件实现(三)

    正文 本文所涉及到的jQuery版本是3.1.1,可以在压缩包中找到event模块.该篇算是阅读笔记,jQuery代码太长.... Dean Edward的addEvent.js 相对于zepto的e ...

  3. JavaScript事件详解-Zepto的事件实现(二)【新增fastclick阅读笔记】

    正文 作者打字速度实在不咋地,源码部分就用图片代替了,都是截图,本文讲解的Zepto版本是1.2.0,在该版本中的event模块与1.1.6基本一致.此文的fastclick理解上在看过博客园各个大神 ...

  4. JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

    二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...

  5. JavaScript事件详解-zepto的事件实现

    zepto的event 可以结合上一篇JavaScript事件详解-原生事件基础(一)综合考虑源码暂且不表,github里还有中文网站都能下到最新版的zepto.整个event模块不长,274行,我们 ...

  6. javascript数组浅谈1

    最近心血来潮要开始玩博客了,刚好也在看数组这块内容,第一篇就只好拿数组开刀了,自己总结的,有什么不对的地方还请批评指正,还有什么没写到的方面也可以提出来我进行完善,谢谢~~ 首先,大概说说数组的基本用 ...

  7. [原创]JavaScript继承详解

    原文链接:http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html 面向对象与基于对象 几乎每个开发人员都有面向对象语言(比如C++. ...

  8. javaScript基础详解(1)

    javaScript基础详解 首先讲javaScript的摆放位置:<script> 与 </script> 可以放在head和body之间,也可以body中或者head中 J ...

  9. javascript设计模式详解之命令模式

    每种设计模式的出现都是为了弥补语言在某方面的不足,解决特定环境下的问题.思想是相通的.只不过不同的设计语言有其特定的实现.对javascript这种动态语言来说,弱类型的特性,与生俱来的多态性,导致某 ...

随机推荐

  1. CentOS7和Ubuntu18.10下运行Qt Creator出现cannot find -lGL的问题的解决方案

    解决方法:缺少相应的opengl的库,需要安装opengl库 一.Ubuntu下解决Qt5.11.1 cannot find -lGL 有两种原因: 一种是没有按照libGL库,那么就安装: sudo ...

  2. 洛谷P2786 英语1(eng1)- 英语作文——map

    给一手链接 https://www.luogu.com.cn/problem/P2786 拿这道题当map模板练练手qwq #include<cstdio> #include<cst ...

  3. 次小生成树(Prim + Kruaskal)

    问题引入: 我们先来回想一下生成树是如何定义的,生成树就是用n - 1条边将图中的所有n个顶点都连通为一个连通分量,这样的边连成子树称为生成树. 最小生成树很明显就是生成树中权值最小的生成树,那么我们 ...

  4. Kubernetes V1.16.2部署Dashboard V2.0(beta5)

    Kubernetes V1.16.2部署Dashboard V2.0(beta5) 在Master上部署Dashboard 集群安装部署请看安装Kubernetes V1.16.2 kubectl g ...

  5. BZOJ 3331 (Tarjan缩点+树上差分)

    题面 传送门 分析 用Tarjan求出割点,对点-双连通分量(v-DCC)进行缩点,图会变成一棵树 注意v-DCC的缩点和e-DCC不同,因为一个割点可能属于多个v-DCC 设图中共有p个割点和t个v ...

  6. Linux服务器安全配置小结(转)

    众所周知,网络安全是一个非常重要的课题,而服务器是网络安全中最关键的环节.Linux被认为是一个比较安全的Internet服务器,作为一种开放源代码操作系统,一旦Linux系统中发现有安全漏洞,Int ...

  7. 左侧菜单收缩的实现(包括,筛选器,addclass、removeclass、绑定事件,链式编程)

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. vue-froala-wysiwyg 富文本编辑器

    近期需要在vue3项目上做一个富文本编辑器,找了很多插件组件,最终决定用 froala.虽然不是免费的,但是功能实在是太强大了. froala 文档:https://www.froala.com/wy ...

  9. vue中监听返回键

    问题:在项目中,我们常常有需求,当用户在填写表单时,点击返回的时候,我们希望加一个弹窗,确认离开吗,确认将保存为草稿 解决方案:利用 H5的 pushstate(个人理解为增加页面栈)特性与onpop ...

  10. PHP内置函数parse_str会自动进行urldecode(URL解码)

    用法:void parse_str ( string $str [, array &$arr] ) parse_str用来解析(分离)URL中的查询字符串(Query String),所谓查询 ...