JavaScript 类型浅解
对于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
,不过圈子很小,它们混的可不是那么好,是出了名的不靠谱。少数情况也是对的,很多情况下是不靠谱的。看看就知道了:
// 靠谱的时候:
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;
// 配合 is 函数使用
if(!isDeep && is(obj, 'Array')) return obj.slice();
for(p in obj) {
var prop = obj[p];
if(!obj.hasOwnProperty(p)) continue;
if(is(prop, 'Object') || is(prop, 'Array')) {
ret[p] = copy(prop, isDeep);
} else {
ret[p] = 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 类型浅解的更多相关文章
- js课程 1-3 Javascript变量类型详解
js课程 1-3 Javascript变量类型详解 一.总结 一句话总结:js对象点(属性方法),json对象冒号(属性方法).属性和方法区别只有一个括号. 1.json对象中的函数的使用? 函数名 ...
- JavaScript事件详解-jQuery的事件实现(三)
正文 本文所涉及到的jQuery版本是3.1.1,可以在压缩包中找到event模块.该篇算是阅读笔记,jQuery代码太长.... Dean Edward的addEvent.js 相对于zepto的e ...
- JavaScript事件详解-Zepto的事件实现(二)【新增fastclick阅读笔记】
正文 作者打字速度实在不咋地,源码部分就用图片代替了,都是截图,本文讲解的Zepto版本是1.2.0,在该版本中的event模块与1.1.6基本一致.此文的fastclick理解上在看过博客园各个大神 ...
- JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解
二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...
- JavaScript事件详解-zepto的事件实现
zepto的event 可以结合上一篇JavaScript事件详解-原生事件基础(一)综合考虑源码暂且不表,github里还有中文网站都能下到最新版的zepto.整个event模块不长,274行,我们 ...
- javascript数组浅谈1
最近心血来潮要开始玩博客了,刚好也在看数组这块内容,第一篇就只好拿数组开刀了,自己总结的,有什么不对的地方还请批评指正,还有什么没写到的方面也可以提出来我进行完善,谢谢~~ 首先,大概说说数组的基本用 ...
- [原创]JavaScript继承详解
原文链接:http://www.cnblogs.com/sanshi/archive/2009/07/08/1519036.html 面向对象与基于对象 几乎每个开发人员都有面向对象语言(比如C++. ...
- javaScript基础详解(1)
javaScript基础详解 首先讲javaScript的摆放位置:<script> 与 </script> 可以放在head和body之间,也可以body中或者head中 J ...
- javascript设计模式详解之命令模式
每种设计模式的出现都是为了弥补语言在某方面的不足,解决特定环境下的问题.思想是相通的.只不过不同的设计语言有其特定的实现.对javascript这种动态语言来说,弱类型的特性,与生俱来的多态性,导致某 ...
随机推荐
- spring-第六篇之创建bean的3种方式
1.创建bean的方式有3种: 1>使用构造器创建bean,即设值注入.构造注入本质都是使用bean的构造器创建bean的. 2>使用静态工厂方法创建bean. 3>调用实例工厂方法 ...
- Java双链表
一.概述 二.英雄类 class HeroNode { //值域 public int id; public String name; public String nickName; //指针域 pu ...
- Java程序流程控制
程序流程控制有 选择,循环,以及跳转结构 选择结构中无非就是 If 和 switch语句我两种都做了一些小案例 1. 利用Scanner库来获得控制台用户输入的数字与代码中定义的变量比较 packag ...
- BZOJ 1912(树的直径+LCA)
题面 传送门 分析 显然,如果不加边,每条边都要走2次,总答案为2(n-1) 考虑k=1的朴素情况: 加一条边(a,b),这条边和树上a->b的路径形成一个环,这个环上的边只需要走一遍,所以答案 ...
- web调试代理工具Whistle
由于最近在学习微信小程序开发,项目中用到了https代理请求,所以用到了基于Node实现的跨平台web调试代理工具Whistle,在此做一记录. 完成https代理请求总共需要5个步骤. 一.安装No ...
- Tensorflow机器学习入门——MINIST数据集识别
参考网站:http://www.tensorfly.cn/tfdoc/tutorials/mnist_beginners.html #自动下载并加载数据 from tensorflow.example ...
- python学习二十一天文件可读,可写,可执行的操作
文件无非是可读,可写,可执行的操作,分别对应的模式 r ,w,x,只读模式,只写模式,只执行模式,a模式为追加模式,实际也是写操作模式,r+,w+,a+ 可读写模式,下面详细说模式的用法 1,文件的模 ...
- navigate连接不上Centos7+mariadb的问题
链接数据库时忽然遇到一个问题.Mac Navicat链接时报错Can’t connect to MySQL server on ‘xx.xx.xx.xx’ (61). PS. win版Navicat ...
- ELKStack之操作深入(中)
ELKStack之操作深入(中) 链接:https://pan.baidu.com/s/1V2aYpB86ZzxL21Hf-AF1rA 提取码:7izv 复制这段内容后打开百度网盘手机App,操作更方 ...
- Codeforces Round #420 (Div. 2) - C
题目链接:http://codeforces.com/contest/821/problem/C 题意:起初有一个栈,给定2*n个命令,其中n个命令是往栈加入元素,另外n个命令是从栈中取出元素.你可以 ...