JS类型判断、对象克隆、数组克隆

22次阅读

共计 6272 个字符,预计需要花费 16 分钟才能阅读完成。

类型判断
我们先说一下 JS 的数据类型,我们一般说 JS 有六大数据类型(ES6 以前)分别是:

基本数据类型

Number
String
Boolean
null
undefined

引用数据类型
object

在 ES6 中新增了 Symbol 数据类型。
有时我们需要知道数据的类型其判断一些事情,我们经常会用 typeof 去判断数据类型。

那么 typeof 能判断什么类型呢?

Number
String
Boolean
object
undefined
function

就这几个数据类型,但这些我们够用吗?或者说准确吗?
我们没有看到 null 那么他是什么类型呢?我们用 typeof null 发现它是 object,是不是很奇怪,其实这是一个 bug,但是这个 bug 是能修复的但是不能修复,因为 null 一般是用来表示一个对象是空的,有时我们用 null 来取消事件,我理解的它好像是一个占位符,表示这个对象是空的。那为什么不能修复呢?因为修复它好说,但是修复了它会带来许多麻烦。
专业点说就是:不同的对象在底层都是用二进制来表示,在 JS 中二进制前三位都是 0 的就会判断为 object 类型,因为 null 全是 0 所以会判断 null 也是 object 类型。
我们来看一下 typeof 判断的情况。
console.log(typeof(1)); //number
console.log(typeof(“1”)); // string
console.log(typeof(true)); // boolean
console.log(typeof({})); // object
console.log(typeof(function (){})); // function
console.log(typeof(null)); // object
console.log(typeof(undefined)); // undefined
当我们想判断一个对象那个是不是 null 或者是不是 Date、RegExp 等类型时会怎么样呢?我们发现都是 object,那我们有没有办法区分他们呢?在这之前我先介绍一下 Object.prototype.toString 这个方法,我相信大家不陌生吧。它也能判断数据类型,但是是这样的。
console.log(Object.prototype.toString.call(1));
console.log(Object.prototype.toString.call(“1”));
console.log(Object.prototype.toString.call(true));
console.log(Object.prototype.toString.call({}));
console.log(Object.prototype.toString.call(function (){}));
console.log(Object.prototype.toString.call(null));
console.log(Object.prototype.toString.call(undefined));
console.log(Object.prototype.toString.call(new RegExp()));
console.log(Object.prototype.toString.call(new Date()));
[object Number]
[object String]
[object Boolean]
[object Object]
[object Function]
[object Null]
[object Undefined]
[object RegExp]
[object Date]
我们发现它比 typeof 高级点,能分辨的更准确,但是格式好像不是我们要的。
接下来进入主题,直接上代码。
// 首先定义好数据类型。
let types = {
“[object Object]”: “Object”,
“[object RegExp]”: “RegExp”,
“[object Date]”: “Date”
};
function type(regs) {
let result = typeof(regs); // 先获取传过来的参数
// 如果是对象在进行判断,不是则返回。
if(result === ‘object’){
if(regs === null){
return ‘null’;
}else{
return types[Object.prototype.toString.call(regs)];
}
}else{
return result;
}
}
console.log(type(1)); //number
console.log(type(“1”)); // string
console.log(type(true)); // boolean
console.log(type({})); // object
console.log(type(function (){})); // function
console.log(type(null)); // null
console.log(type(undefined)); // undefined
console.log(type(new RegExp())); //RegExp
console.log(type(new Date())); //Date
对象克隆
我们经常会用到一个对象去做一些事情,可能有时候我们不想改变原有的数据。,时候我们就需要对象克隆了,你可能简单的以为就是 = 就行了,那我们来看一看。
let obj = {
a: 1
}
let obj2 = obj;
console.log(obj); //{a: 1}
console.log(obj2); //{a: 1}
我们看到复制过来了,这样我们就可以随便使用了。那我们来修改一下 obj 看看。
obj.a = 2;
console.log(obj); //{a: 2}
console.log(obj2); //{a: 2}
发现都变成了 {a: 2},是不是很奇怪。因为对象是引用类型的,他们赋值其实是赋的地址值,就是他们指向同一个地方,那么我们应该怎么做呢?你应该知道怎么遍历对象,我们就把它遍历一遍再复制。看代码
let obj = {
a: 1
}
function clone(obj) {
let a = {};
for(let o in obj){
a[o] = obj[o]
}
return a;
}
let obj2 = clone(obj);
console.log(obj); //{a: 1}
console.log(obj2); //{a: 1}
obj.a = 2;
console.log(obj); //{a: 2}
console.log(obj2); //{a: 1}
没有改变,看来我们成功了,那这篇文章就到这了。呵呵,其实远没有,我们来看一下有没有什么问题。
当里面的数据为引用类型时:
let obj = {
a: {
b: 1
},
c: 3
}
function clone(obj) {
let a = {};
for(let o in obj){
a[o] = obj[o]
}
return a;
}
let obj2 = clone(obj);
console.log(obj);
console.log(obj2);
obj.a .b = 2;
console.log(obj);
console.log(obj2);
我们发现又出问题了。
如果你知道 for…in 你就会知道它的另一个错误。就是它会遍历它原型上的可枚举属性和非 Symbol 的属性。那么我们怎么改善一下呢?现在介绍一下 hasOwnProperty 这个属性,它就是判断自身有没有这个属性,而不会去原型上找。
function clone(obj) {
let a = {};
for(let o in obj){
if(obj.hasOwnProperty(o)){
a[o] = obj[o];
}
}
return a;
}
这个问题解决了,就差上一个了,我们接着用判断数据的类型来判断是否还需要复制的方法解决上一个问题。
let obj = {
a: {
b: 1,
d: {
e:[{f: 2}],
g: {
h:{
l: 5
}
}
}
},
c: 3
}

function deepClone(origin, target) {
let tar = target || {},
arr = “[object Array]”,
str = Object.prototype.toString;
for(let o in origin){
if(origin.hasOwnProperty(o)){
// 如果是对象接着递归复制
if(typeof origin[o] === ‘object’){
// 判断是对象还是数组
tar[o] = str.call(origin[o]) === arr ? [] : {};
deepClone(origin[o], tar[o]);
}else{
tar[o] = origin[o];
}
}
}
return tar;
}
let obj2 = deepClone(obj, {});
console.log(obj);
console.log(obj2);
obj.a.d.g.h.l = 6;
console.log(obj.a.d.g.h.l); //6
console.log(obj2.a.d.g.h.l); //5

其实这还不是最终的深克隆,因为这一个也有它自己的问题,但是面对一般的情况应该没问题,跟高级的用法请自行学习。
模拟实现 JQ 的 $.extend() 方法(只是粗略的写了一下, 如有错误欢迎指出):

function extend() {
let origin, // 要拷贝的源
target = arguments[0], // 获取第一个参数
isDeepClone = false; // 是否深拷贝
length = arguments.length, // 拷贝的个数
arr = “[object Array]”,
str = Object.prototype.toString,
i = 0;
if(typeof target === ‘boolean’){
isDeepClone = target;
i ++;
target = arguments[i]; // 获取目标元素
}
// 防止循环引用
if(origin === target){
return;
}
// 兼容 function
if(typeof target !== ‘object’ && typeof target !== ‘function’){
target = {};
}
for (; i < length; i++) {
origin = arguments[i];
for(let o in origin){
if(origin.hasOwnProperty(o)){
if(origin[o] === ‘object’){
if(isDeepClone){
target[o] = str.call(origin[o]) === arr ? [] : {};
extend(true, target[o], origin[o]);
}
}else{
target[o] = origin[o];
}
}
}
}
return target;
}
补充:其实不止这一种深克隆的方法,不如我们处理数据最常使用的 JSON
let obj = {
a: {
b: function (argument) {

},
d: {
e:[{f: 2}],
g: {
h:{
l: 5
}
}
}
},
c: 3
}
let r = JSON.stringify(obj);
r = JSON.parse(r);
obj.a.d.g.h.l = 6;
console.log(r.a.d.g.h.l); // 5
也是可以的,我们输出一下 r 看看。
有没有发现少了什么?对,就是 function,它不仅不能复制 function 还有 undefined 也不行,还有别的自己查一下吧。
3. 数组克隆
有了上面的铺垫,我们知道数组也是引用类型,就不能简单的等于来复制。
concat
let arr = [8,5,6,6,8];
let arr2 = arr.concat();
arr2[3] = 1;
console.log(arr); //[8, 5, 6, 6, 8]
console.log(arr2); //[8, 5, 6, 1, 8]
可以复制成功,那么引用类型呢?
let arr = [8,{a: 1},6,6,8];
let arr2 = arr.concat();
arr2[1].a = 2;
console.log(arr);
console.log(arr2);

还有我们常用的 slice 也是一样
let arr = [8,{a: 1},6,6,8];
let arr2 = arr.slice();
arr2[1].a = 2;
arr2[2] = 2;
console.log(arr);
console.log(arr2);
还有一些别的方法,我就不一一列举了,这些都是浅复制。
如果想深度克隆数组,也可以使用上面介绍的使用 JSON 也是可以的。
let arr = [8,{a: 1},6,6,8];
let arr2 = JSON.parse(JSON.stringify(arr) );
arr2[1].a = 2;
arr2[2] = 2;
console.log(arr);
console.log(arr2);

目前想到的就这些,总感觉拉下了什么,如果我想起来了我会继续补充的。
4. 闲聊
上面写的我意犹未尽,可能是自己知识的局限性暂时只能想到那些,上面说到了 for…in,那么我们来简单的说一下 for…of 和它的区别。
他们都是遍历用的,每次遍历数组和对象都会想起它们,那么你会不会弄混呢。
那我们直接遍历一次,看看有什么区别。
let arr = [8,{a: 1},6,6,8];
let a = {
b:1,
r: 8,
h:{
e:6
}
}
console.log(‘for…of’);
for(let i of arr){
console.log(i);
}
console.log(‘for…in’);
for(let i in a){
console.log(‘key:’ + i);
}
是不是感觉挺好的,我们再来看看。
let arr = [8,{a: 1},6,6,8];
let a = {
b:1,
r: 8,
h:{
e:6
}
}
console.log(‘for…in 遍历数组 ’);
for(let i in arr){
console.log(i);
}
console.log(‘for…of 遍历对象 ’);
for(let i of a){
console.log(‘key:’ + i);
}

for…of 遍历对象直接报错了,你有没有注意到报错的信息。就是不可遍历,因为不是 iterable。数组、字符串、Set、Map,内置好了 Iterator(迭代器),它们的原型中都有一个 Symbol.iterator 方法,而 Object 对象并没有实现这个接口,使得它无法被 for…of 遍历。
至于 for…of 遍历对象就需要实现 Iterator,这里我就不写了,百度好多。
for…in 遍历数组遍历出来的是索引。不过我们也可以得到数组的值。

for(let i in arr){
console.log(arr[i]); //[8,{a: 1},6,6,8]
}
我觉得你看到这里应该知道遍历什么用哪个更合适了。

补充

__proto__的实现
Object.defineProperty(Object.prototype, __proto__, {
get: function(){
return Object.getPrototypeOf(this);
},
set: function(ob){
Object.setPrototypeOf(this, ob);
return ob;

})
“`
下一篇文章我想说一下数组去重好像不是最全的数组去重方法,因为内容挺多,我就不一起写了,喜欢的可以点一个赞,或者关注一下。鼓励一下一名自学前端的大学生。

正文完
 0