数据类型判断
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);
弊端:
- 拷贝的对象的值如果有函数、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;
}
弊端:
- 不能复制不可枚举的属性以及Symbol类型
- 只能对普通的引用类型的值做递归复制,对于数组、日期函数、正则、错误对象、function这样的引用类型不能正确的拷贝
- 对象的属性里面成环,即循环引用并没有解决
不可枚举的属性:对象的属性可以分为可枚举属性和不可枚举属性,可枚举属性能够被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类型代表一个独一无二的值
- 改进版深拷贝(使用递归实现)
- 针对能够遍历对象的不可枚举属性和Symbol类型使用Reflect.ownKeys方法,该方法返回一个由目标对象自身的属性键组成的数组。
- 当参数为Date、RegExp类型时,则直接生成一个新的实例返回。
- 利用Object的getOwnPropertyDescriptors方法可以获得对象的所有属性,以及对应的特性,顺便结合Object的create方法创建一个新对象,并继承传入原对象的原型链
- 利用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是弱引用,如果不再使用,内存空间直接释放
深拷贝总结:
- WeakMap和Map的区别:区别就在于WeakMap是弱映射,WeakMap只接受对象作为键名(null 除外) ,不接受其他类型的值作为键名。
- 对象的垃圾回收机制: JS中对象存储在堆中,当该对象内存没有被指向时就会被回收
- Map的强引用与WeakMap的弱引用: 在Map中如果用对象作为键,那么就是强引用,Map实例也会指向对象内存,而WeakMap的实例就不会指向对象内存。
WeakMap的应用场景:
- DOM节点元数据