JavaScript基础及进阶(持续更新中)

数据类型判断

null undefined object NAN

  • typeof
    缺点:对于object类型只能判断出function,其余都返回’object’
    console.log(typeof null) // 'object'
    let arr = [1,2,3,4]
    console.log(typeof arr) // 'object'
  • instanceof

instanceof:定义instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链的任何位置。

prototype:类似于一个指针,创建的每一个函数都有prototype属性,这个指针指向一个对象,这个对象包含了通过调用该构造函数所创建的对象共享的属性和方法,prototype被该函数调用。

Object.getPrototypeOf: 用在被构造函数创建的实例上

isPrototypeOf: 用来判断某个实例和某个原型对象之间是否有联系

hasOwnProperty: 用来检测一个属性是存在于实例中还是原型中

        function Person() { }
        Person.prototype.name = 'bangbang';
        Person.prototype.age = 18;
        Person.prototype.job = 'programmer';
        Person.prototype.dream = function () {
            console.log('Change yourself');
        }
        let person1 = new Person();
        let t = Object.getPrototypeOf(person1) //获得的是person的原型对象
        // 此时t就是一个对象,又因为对象都是Object的实例,因此getPrototypeOf(t)返回的就是Object的原型对象
        console.log(Object.getPrototypeOf(t)===Object.prototype); // true
        let tt = Object.getPrototypeOf(t); // 此时tt为Object的原型对象
        console.log(Object.getPrototypeOf(tt)); // null 
        console.log(Person.prototype.isPrototypeOf(person1)); // true
        console.log(person1.hasOwnProperty('name')) // false
        console.log(person1.name); // 'bangbang'
        person1.name = "ll"; // 新创的属性如果与原型对象上某属性重名,则覆盖
        console.log(person1.name); // 'll'
        console.log(person1.hasOwnProperty('name')) // true
        console.log(person1 instanceof Person); // true

手写instanceof函数

function myInstanceof(left,right) {
  if(typeof left != 'object'|| left == null) return false;
  let pro = Object.getPrototypeOf(left);
  while (true) {
      if(pro === null) return false;
      if(pro === right.prototype) return true;
      pro = Object.getPrototypeOf(pro);
  }
}
console.log(myInstanceof(new Number(123),Number));
  • Object.prototype.toString
    为判断类型的最佳实践
    通过call来改变this指向
    console.log(Object.prototype.toString.call(1)) // [object Number]

数据类型转换

强制类型转换

隐式类型转换

Object的转换规则

  • 如果有Symbol.toPrimitive方法则由优先调用该方法然后返回
  • 调用valueOf(),如果转换为基础类型则返回
  • 调用toString(),如果转换为基础类型则返回
  • 如果都没有返回基础类型会报错
    let obj = {
        value: 1,
        valueOf(){
            return 2;
        },
        toString(){
            return '3';
        },
        [Symbol.toPrimitive](){
            return 4;
        }
    }
    console.log(obj + 1); // 5
    console.log(10 + {}); // 调用valueOf,返回对象本身{},不是基础类型继续调用toString,得到"[object Object]",和10相加,得到"10[object Object]"。
    console.log([1,2,undefined,4,5]+10);
    // "1,2,,4,510"

深浅拷贝

浅拷贝

  • Object.assgin(target,source) (拷贝对象)
  • 扩展运算符 (拷贝对象或数组)
let obj = {a:1,b:{c:1}}
let obj2 = {...obj}
  • concat拷贝数组
let arr = [1,2,3,4]
let arr1 = arr.concat()
  • slice拷贝数组:arr.slice(begin,end) 、arr.slice()

引用类型存在于堆中

以上的方法为浅拷贝,在对象或数组中如果不存在对象或数组作为元素,其和深拷贝有着一样的效果,即拷贝的值不会随着原值的改变而改变。

手写浅拷贝

   const shallowClone = (target) => {
        if (typeof target == 'object' && target !== null) {
            let tmp = Array.isArray(target) ? [] : {};
            for (const key in target) {
                if (target.hasOwnProperty(key)) {
                    tmp[key] = target[key];
                }
            }
            return tmp;
        } else {
            return target;
        }
   } 

深拷贝

浅拷贝只是开辟了一个新的对象,并把原对象中的基本类型复制了过来

而深拷贝则是在堆内存中完全开辟了一块内存地址,并把原对象完全复制过来存放

  • JSON.stringfy(obj) + JSON.parse(str)
    let obj = {a:1,b:[1,2,3]};
    let str = JSON.stringfy(obj);
    let obj1 = JSON.parse(str);

弊端:

  1. 拷贝的对象的值如果有函数、undefined、symbol,则经过JSON.stringfy序列化之后的字符串中这个键值对会消失
    const obj = {
        a:function () {
        console.log('Change yourself');
     },
        b: 2
    }
    let str = JSON.stringify(obj);
    console.log(str);
    let obj1 = JSON.parse(str);
    console.log(obj1); // {b: 2}

2.拷贝Date引用类型会变成字符串

  • 手写深拷贝(基础版)
    let obj1 = {
        a:{
            b:1
        }
    };
    function deepClone(obj) {
        let cloneObj = {};
        for(const key in obj){
            if(typeof obj[key] === 'object'){
                cloneObj[key] = deepClone(obj[key]);
            }else{
                cloneObj[key] = obj[key];
            }
        }
        return cloneObj;
    }

弊端:

  1. 不能复制不可枚举的属性以及Symbol类型
  2. 只能对普通的引用类型的值做递归复制,对于数组、日期函数、正则、错误对象、function这样的引用类型不能正确的拷贝
  3. 对象的属性里面成环,即循环引用并没有解决

不可枚举的属性:对象的属性可以分为可枚举属性和不可枚举属性,可枚举属性能够被for…in…枚举到

    let dog = {
        name: "xiaohuang",
        sound: "wangwang"
    };
   Object.defineProperty(dog,"zhuren",{
    enumerable: false, // 设置为不可枚举属性
    configurabele: false, // 设置该属性的描述符不能被改变且该属性不能被删除
    writable: false, // 设置该属性的value值不能被修改
    value: "zz"
   })
    for(const key in dog) {
        console.log(key);
    } 
    // name
    // sound

Symbol类型:用Symbol类型代表一个独一无二的值

  • 改进版深拷贝(使用递归实现)
  1. 针对能够遍历对象的不可枚举属性和Symbol类型使用Reflect.ownKeys方法,该方法返回一个由目标对象自身的属性键组成的数组。
  2. 当参数为Date、RegExp类型时,则直接生成一个新的实例返回。
  3. 利用Object的getOwnPropertyDescriptors方法可以获得对象的所有属性,以及对应的特性,顺便结合Object的create方法创建一个新对象,并继承传入原对象的原型链
  4. 利用WeakMap类型作为Hash表,因为WeakMap是弱引用类型,可以有效防止内存泄露,作为检测循环引用很有帮助,如果存在循环,则引用直接返回WeakMap存储的值
    const isComplexDataType = obj => (typeof obj ==='object'||typeof obj === 'function')&&(obj !== null)

    const deepClone = function (obj,hash=new WeakMap()) {
        if(obj.constructor === Date) return new Date(obj)
        if(obj.constructor === RegExp) return new RegExp(obj)
        if(hash.has(obj)) return hash.get(obj)
        let allDesc = Object.getOwnPropertyDescriptors(obj)
        let cloneObj = Object.create(Object.getPrototypeOf(obj),allDesc)
        hash.set(obj,cloneObj)
        for(let key of Reflect.ownKeys(obj)) {
            cloneObj[key] = (isComplexDataType(obj[key])&&typeof obj[key]!=='function') ? deepClone(obj[key],hash) : obj[key]
        }
        return cloneObj
    }
    let obj = {
        num: 0,
        str: '',
        boolean: true,
        unf: undefined,
        nul: null,
        obj: {name:'我是一个对象',id:1},
        arr:[0,1,2],
        func: function(){console.log('我是一个函数')},
        date: new Date(0),
        reg: new RegExp('/我是一个正则/ig'),
        [Symbol('1')]:1
    }
    Object.defineProperty(obj,'innumerable',{
        enumerable:false,value:'不可枚举属性'
    })
    obj = Object.create(obj,Object.getOwnPropertyDescriptors(obj))
    obj.loop = obj
    let cloneObj = deepClone(obj)
    cloneObj.arr.push(4)
    console.log('obj',obj)
    console.log('cloneObj',cloneObj)

我们选择WeekMap来记录对象是否被克隆,主要考虑以下三点:
WeakMap对象是key => value形式,不会重复记录
WeakMap对象的key必须是一个对象
WeakMap是弱引用,如果不再使用,内存空间直接释放

深拷贝总结:

  1. WeakMap和Map的区别:区别就在于WeakMap是弱映射,WeakMap只接受对象作为键名(null 除外) ,不接受其他类型的值作为键名。
  2. 对象的垃圾回收机制: JS中对象存储在堆中,当该对象内存没有被指向时就会被回收
  3. Map的强引用与WeakMap的弱引用: 在Map中如果用对象作为键,那么就是强引用,Map实例也会指向对象内存,而WeakMap的实例就不会指向对象内存。

WeakMap的应用场景:

  1. DOM节点元数据
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

csdoge

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值