资讯专栏INFORMATION COLUMN

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

dreamtecher / 1317人阅读

摘要:对象克隆我们经常会用到一个对象去做一些事情,可能有时候我们不想改变原有的数据。如果是对象接着递归复制判断是对象还是数组其实这还不是最终的深克隆,因为这一个也有它自己的问题,但是面对一般的情况应该没问题,跟高级的用法请自行学习。

类型判断

我们先说一下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;

})
 ```

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100699.html

相关文章

  • 【转】JavaScript 对象的深度克隆

    摘要:在聊以下简称深度克隆之前,我们先来了解一下中对象的组成。克隆或者拷贝分为种浅度克隆深度克隆。浅度克隆基本类型为值传递,对象仍为引用传递。 该文转载自http://www.cnblogs.com/zichi/p/4568150.html,有部分修改。 在聊JavaScript(以下简称js)深度克隆之前,我们先来了解一下js中对象的组成。在 js 中一切实例皆是对象,具体分为 原始类型 ...

    JowayYoung 评论0 收藏0
  • js对象详解(JavaScript对象深度剖析,深度理解js对象)

    摘要:对象详解对象深度剖析,深度理解对象这算是酝酿很久的一篇文章了。用空构造函数设置类名每个对象都共享相同属性每个对象共享一个方法版本,省内存。 js对象详解(JavaScript对象深度剖析,深度理解js对象) 这算是酝酿很久的一篇文章了。 JavaScript作为一个基于对象(没有类的概念)的语言,从入门到精通到放弃一直会被对象这个问题围绕。 平时发的文章基本都是开发中遇到的问题和对...

    CatalpaFlat 评论0 收藏0
  • ES6时代,你真的会克隆对象吗(二)

    摘要:多个窗口意味着多个全局环境,不同的全局环境拥有不同的全局对象,从而拥有不同的内置类型构造函数。比如,表达式会返回,因为属性得到的仅仅是构造函数,而且是可以被手动更改的,只是返回的构造函数的名字,它并不返回类名。 原文:ES6时代,你真的会克隆对象吗(二) 上一篇,我们从Symbol和是否可枚举以及属性描述符的角度分析了ES6下怎么浅拷贝一个对象,发表在掘金和segmentfault上(...

    BoYang 评论0 收藏0
  • ES6时代,你真的会克隆对象吗?

    摘要:原文你真的会克隆对象吗开始之前在开始聊克隆之前,我们还是先来看看数据类型。值通过函数生成,是独一无二的。同时,中规定了对象的属性名有两种类型,一种是字符串,另一种就是类型。返回一个数组,包含对象自身的所有属性的键名。 原文:你真的会克隆对象吗 开始之前 在开始聊克隆之前,我们还是先来看看js数据类型。js的数据类型分为基本数据类型和复杂数据类型。 基本数据类型:Number、Bool...

    xiaokai 评论0 收藏0
  • js克隆一个对象,支持循环引用的克隆

    摘要:判断参数是否为待判断的参数克隆一个对象要克隆的目标对象克隆节点,绑定事件的有问题,暂不处理克隆在当前作用域,在全局克隆其它对象,通过识别复制后的对象与原对象是否相同来决定传不传参数,像数组是不能传参数的使用防止对象重写了方法支持节点克隆 (function(){ var toString=Object.prototype.toString,gObj={},cloneHelper=f...

    fai1017 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<