【大白话】说JS的实际应用之变量类型,深浅拷贝,ES6特性

127 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

前言

大白话说JS内容包括:DOM的一些操作,Promise相关, 微任务宏任务,作用域,变量提升,闭包,变量类型,深浅拷贝,原型和作用域链,后续争取把js重点都记录上,深入浅出。

变量类型,深浅拷贝

常知的变量类型有:基本类型(String、Number、Boolean、Symbol、Undefined、Null,BigInt),和引用类型(object , array ,function )。基本类型在桟中执行,使用具体数值直接存储;引用类型则在堆中,且 存储的数据是地址 而非具体数值。

顺带说下类型的判断:

  • typeof:分不清普通对象(typeof null =object)还是数组对象( typeof []=object)
  • instanceof:用来测A 是不是B的原型的,能清楚判断对象类型但也只能测对象类型
  • constructor: 利用创建时产生的原型链追溯类型,null 和 undefined 是无效的对象,所以测不了
  • Object.prototype.toString.call():都能测
//类型判断
typeof("")

[] instanceof Array;// true
{} instanceof Object;// true
newDate() instanceof Date;// true

let arr=[];
console.log(b.constructor === Array);
console.log("".constructor === String);

Object.prototype.toString.call(newDate()) ;// [object Date]
Object.prototype.toString.call([]) ;// [object Array]

// 基础类型
let a = 10;
let b = a;
a = 20;
console.log('b:', b);  //输出b: 10

// 引用类型
let c = { age: 20 };
let d = c;
c.age = 21;
console.log('d:', d);  //输出d: { age: 21 }

在上面的例子中,原始类型的变量有私有内存各自存储数值,一方的改变不会牵动自身;引用类型d指向c的存储地址,c值改变后,d用的依旧是c的地址所以输出21。

上面其实引出了深拷贝和浅拷贝的概念,浅拷贝:复制的同时一方变另一放跟着变(修改的是栈内存中的同一个值),深拷贝,完全复制了一个对象,一方的改变不会牵动另一方(修改堆的不同的值)。 实际项目中常用 let newArray = [...Array] 来浅拷贝对象。

如何实现浅拷贝? 如果是数组,可以使用slice,concat,Array.from , 展开运算符 ,来返回一个新数组的特性实现拷贝。但是如果数组嵌套了其他引用类型, concat 方法将复制不完整。

原理:

function copy (obj){
    if(typeof obj === 'object' && obj !== '' ){
		let copy = Array.isArray(obj) ? [] :{};
        for(var p in obj){
            copy[p]=obj[p];
        }
        return copy;  
    }
    else {return obj;}
}

例子:

var obj = {
  A: '1',
  B: {
    b: '12',
   	c: 28,
  },
}

var copy1 = [];
Object.assign(copy,obj);//或者 var copy = Object.assign({},obj);
var copy2 = [].concat(obj);
var copy3 = {...obj};

copy.color = '33'; // 改变原数据第一层属性 A (基本数据类型)
copy.person.name = '44'; // 改变原数据第一层属性 B(引用数据类型)

console.log(obj,copy); // 原数据的引用类型会改变,copy全变

深拷贝才是重点:

JSON.parse(JSON.stringify()) :如果原对象中有undefined、Symbol、函数时,会导致该键值被丢失,有RegExp、Error对象,会被转换为空对象{},有Date,会被转换成字符串),有NaN、Infinity和-Infinity,则序列化的结果会变成null,一句话,它只能序列化对象的可枚举的自有属性,有局限性。

所以在项目中,我们一般用现有的深拷贝库,如lodash的Api,:_.cloneDeep: 直接克隆

var new_arr = JSON.parse( JSON.stringify(old_arr) );

原理是JOSN对象中的stringify可以把一个js对象序列化为一个JSON字符串,parse可以把JSON字符串反序列化为一个js对象,通过这两个方法,也可以实现对象的深复制,但是这个方法不能够拷贝函数 。

手写一个深拷贝(巩固理解):

function deepClone (obj) {
  let objClone = Array.isArray(obj) ? [] : {};
  if (obj && typeof obj === "object") {
    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
        //判断ojb子元素是否为对象,如果是,递归复制,如果不是,简单复制
       objClone[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
      }
    }
  }
  return objClone;
}
let a = [1, 2, 3, 4],
  b = deepClone(a);
a[0] = 2;
console.log(a, b);

在这里插入图片描述

深拷贝是拷贝对象各个层级的属性,看对象的属性是否是对象或数组类型,进行相应属性的一一复制。

ES6新增特性:

  • let和const(看变量提升,块级作用域,能否重定义),以及Symbol,Bigint变量。

  • 解构赋值:let arr = [1,2,3,4,5]; const [a,b,c,d,e] = arr; 业务中很常用

  • 模块化:export default { 暴露的对象 } 导出; import { a } from ‘暴露的对象’ 导入

  • 拓展运算符(...):可以将多个数组或对象解构形成一个新元素(在去重,浅拷贝,解构很常用) : const c = [...new Set([...a,...b])];

  • 箭头函数:()=>{} ,箭头函数和普通函数的区别(this,实例化,argument)

  • 扁平化数组Object.values(数组).flat(Infinity),Infinity为数组的维度,flat不支持IE浏览器

  • 模版字符串:let str3=我爱中国的${str2}

  • 数组新增的api:原来的:传送门 新增:(includes,startsWith,endsWith)

  • 双问号??,与||作用相同:作用就是判断该对象有没有默认值,没有就采取问号后面的值,比如输入框非空判断if((value ?? '') !== '')?.则是判断该对象有无该属性或方法 obj?.eat,没有就返回undefined,不会报错,在业务中算是常用,(在TS中,还有!.就告诉编译器,一定有这属性,别给我报这属性可能不存在的错)