这是我参与8月更文挑战的第9天,活动详情查看: 8月更文挑战”juejin.cn/post/698796…
数据类型
分为基本类型数据和引用类型数据
基本类型数据
- String、Number、Boolean、Null、Undefined、Symbol、BigInt
- 值保存在栈内存中
引用类型数据
-
Object
- Math、Date、RegExp、Function、Array
-
值保存在堆内存里,栈内存里保存的是引用地址。
数据类型检测
数据类型的检测方法可分为三种,分别为:
- typeof
- insatnceof
- toString
typeof
-
基本类型数据除 null 以外,可正确返回数据类型;
- typeof null "object" 。是js本身设计的一个bug,如果需要判断数据是不是null,可直接 xxx === null 来判断
-
引用类型数据除 function 以外,其他几种都返回 object;
- typeof function "function"
insatnceof
此方法源于原型链相关知识。
new 构造函数的时候,新生成的对象就继承了构造函数的属性和方法。在原型链上产生的结果就是新对象的 proto 指向构造函数的prototype,所以可以通过 instanceof 来判断新生成的对象是否是某个构造函数的实例。
var ObjectFactory = function () {
// 创建一个对象
var obj = new Object()
// 返回第一个参数作为构造函数
var Constructor = [].shift.call(arguments)
// 将构造函数的原型复制于对象的原型
obj.__proto__ = Constructor.prototype
// 调用构造函数,并将obj 作为this, arguments作为参数
var ret = Constructor.apply(obj, arguments)
// 如果构造函数里返回一个对象的话,就直接返回,否则我们就返回this即new创建的对象
return typeof ret === 'object'? ret: obj
}
// 效果等效
var a = ObjectFactory(Person, 'sven');
- 基本类型数据都返回 false,结果不够精确
- 引用类型数据返回true,比较准确
instanceof代码实现
function myInstanceof(left,right){
// 先用typeof判断是否是基本数据类型,如果是就返回false
if(typeof left !== "object" || left=== null){return false;}
// getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
let proto = Object.getPrototypeOf(left);
while(true){
// 循环向下寻找,直到找到相同的原型的对象
if(proto===null){return false;}
if(proto === right.prototype){return true;}
proto = Object.getPrototypeOf(proto);
}
}
toString
Object.prototype.toString()方法是 Object 的原型方法,返回格式为 [Object Xxx] (首字母大写)
- 返回的数据类型很准确,甚至可以将 window 和 document 区分开
- 除普通 object 对象外,其他数据类型的值调用是需要加 call 方法
Object.prototype.toString.call(xxx)
全局通用的数据类型判断方法
function getType(obj){
let type = typeof obj;
if(type !== "object"){ // 先进行typeof判断,如果是基础数据类型,直接返回
return type;
}
// 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
return Object.prototype.toString().call(obj).replace(/^[Object (\S+)]$/,'$1');
}
深浅拷贝
针对的都是第一层数据,而不是最外层整体的数据。
浅拷贝
自己创建一个新的对象,来接受你要重新复制或引用的对象值
- 基本类型数据:直接把值复制一份赋给新的数据
- 引用类型数据:复制过去的是值的引用地址。所以如果新的数据里修改了引用类型的值,原数据也会变
深拷贝
将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。
- 基本类型数据:直接把值复制一份赋给新的数据
- 引用类型数据:直接把堆内存中数据复制一份到新的内存中,所以如果新的数据里修改了引用类型的值,原数据不会变
浅拷贝方法
object.assign()
- 两个参数:target source(可以是多个) 无返回值
- 如果拷贝后修改源对象的值,那么在修改之前输出,也是改变后的值
注意点:
- 不拷贝不可枚举属性
- 不拷贝继承属性
- 可拷贝 Symbol 类型的属性值
可以理解为内部使用循环遍历原对象的属性,将原对象的属性赋值给新对象的相应属性。
扩展运算符
直接在新对象中展开数据,即可。
- 当数据第一层都为基本数据类型时,用扩展运算符比较方便
concat()、slice()
专用于数组的浅拷贝方法
arr.concat();
arr.slice();
手动实现浅拷贝方法
function shallowClone(target){
if(typeof target === "object" && target !== null){
let cloneTarget = Array.isArray(target) ? [] : {};
for(let prop in target){
if(target.hasOwnProperty(prop)){
cloneTarget[prop] = target(prop);
}
}
return cloneTarget;
} else {
return target;
}
}
深拷贝方法
JSON.stringify()
把一个对象序列化成为 JSON 的字符串,并将对象里面的内容转换成字符串,最后再用 JSON.parse() 的方法将JSON 字符串生成一个新的对象
注意点:
- 如果对象中有函数、undefined、symbol等类型数据,拷贝后键值对会消失
- 拷贝Date类型数据会变为字符串
- 拷贝RegExp对象会成为空对象
- 不可拷贝不可枚举属性
- 不可拷贝对象原型链
- 如果对象中有NaN、infinity等数据,拷贝后会成为null
- 无法拷贝对象的循环引用
手写基础版深拷贝
function deepClone(target){
let cloneObj = {};
for(let prop in target){
if(typeof target[prop] === "object" && target[prop] !== null){
cloneObj[prop] = deepClone(target[prop]);
} else {
cloneObj[prop] = target[prop];
}
return cloneObj;
}
- 仍不能复制不可枚举属性和symbol类型的值
- 仅针对普通object对象进行深拷贝,对function、date、math等仍无法进行深拷贝
- 对象的循环引用无法解决
手写最终版深拷贝
// 严格判断数据是否是引用类型数据
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); // 正则对象直接返回一个新的正则对象
// 如果循环引用了就用 weakMap 来解决
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;
}