深夜爆肝JS好文! (一)

简介: 笔记

1. 深拷贝与浅拷贝


1.1 深拷贝与浅拷贝有什么区别?


深拷贝和浅拷贝只针对象: Object, Array 这样的复杂对象(引用数据类型),基本数据类型不存在深浅拷贝,只存在赋值操作。

简单来说,浅拷贝只复制一层对象的属性,而深拷贝则递归复制了所有层级。

举个例子:


比如A和B分手了,如果这两人分手后还有联系,藕断丝连,那就是浅拷贝,如果 分手之后再无联系,各自过各自的生活,互不影响,这叫深拷贝。1.png

1.2 解构是深拷贝吗?为什么?


这里就是面试官给你挖坑了,考察你的基础扎实程度。

我们先来看一组代码:

数组解构赋值


let arr2 = [1,2,3,4];
let newArr2 = [...arr2]; //此处是解构赋值
newArr2.push(5);
console.log(arr2, newArr2) // arr2的结果: 1,2,3,4 newArr2的结果: 1,2,3,4,5

对象解构赋值

let objA = {
  name: '小明',
  age: 8
}
let {name , age} = objA;
name = '小花';
age = 10;
console.log(name, age, objA); // 1.name为小花,2.age为10 3.objA没有变化
  • 据上可得:无论是数组还是对象,经过解构赋值之后的新变量,两者之间相互没有影响,那么我们是否可以下定义了:解构赋值是深拷贝?
  • 不要急,我们再来看一组代码:
let arr3 = [[1,2],[3,4],[5,6]];
let newArr3 = [...arr3];
newArr3[0][1] = 33333;
console.log(newArr2, arr2)

2.png我们可以看到:两组数据相互影响了

那么问题就来了,为什么上面的代码可以实现互不影响,下面却不行?

答案是:解构不是真正的深拷贝,是伪深拷贝

结论:解构只能深拷贝一维数组与一维对象,多维数组和对象无效

1.3 如何实现一个深拷贝?


这里有两种方式


JSON()的相关API,代码如下:

let list = [
    {id: 1, stuName: '小明', class:'五年二班'},
    {id: 2, stuName: '小红', class:'五年三班'},
    {id: 3, stuName: '小绿', class:'五年四班'}
]
// JSON.stringify : 对象转json字符串
// JSON.parse : 将json字符串转换成json对象
let newList = JSON.parse(JSON.stringify(list));
let newObj = {
  id: 4,
  stuName: '小白',
  class: '五年四班'
}   
newList.push(newObj)
newList[2].class = '六年一班';
console.log(list, newList)

打印结果如下:3.png由此可见,JSON相关方法,确实可以实现深拷贝。

但是此类方法只能适用于部分场景

如果对象里有 function这种关键字是不行的,JSON()方法会将它转化为字符串,那么,自然不行,性质都不一样了


2. 用原生JS写一个真正的深拷贝,适用于所有场景:


原理简单粗暴:将对象里的值一个一个取过来,重新定义

代码如下:

// 标准的深拷贝
function deepClone(source){
   // 判断复制的目标是数组还是对象
   const targetObj = source.constructor === Array ? [] : {}; 
   // 遍历目标
   for(let keys in source){ 
     if(source.hasOwnProperty(keys)){
       // 如果值是对象,就递归一下
       if(source[keys] && typeof source[keys] === 'object'){ 
         targetObj[keys] = source[keys].constructor === Array ? [] : {};
         targetObj[keys] = deepClone(source[keys]);
       }else{ 
         // 如果不是,就直接赋值 (基本类型)
         targetObj[keys] = source[keys];
       }
     }
   }
   return targetObj;
 }
  • 测试是否管用:
let objD = {
      name: '小猪',
      age: 20,
      detail: {
        color: '白色',
        type: '种猪'
      }
    }
    let newObjD = deepClone(objD);
    newObjD.detail.color = "黑色";
    console.log(objD, newObjD)

实际结果:4.png实验表明,确实是管用的

FAQ:深拷贝的代码务必手写下来,真的很重要


2. 原型与原型链


1.1 原型是什么?有什么用?


原型 prototype是 js 中极其重要的概念之一,也是面试高频问题点,也是比较容易引起混淆的地方

原型的概念:

首先,我们明确原型是一个对象

每个函数都有一个属性叫做原型(函数特有的),这个属性指向一个对象

原型是函数对象的属性,不是所有对象的属性,对象经过构造函数new出来,那么这个new出来的对象的构造函数有一个属性叫原型

当哦我们定义一个函数的时候,这个函数的原型属性也就被定义出来了,并且可以使用它。如果不对它进行显示赋值的话,那么它的初始值就是一个空的对象Object(默认添加的)

简单来说:原型是函数的一个特有属性

function fn1(a , b){
  return a*b;
}
console.log(fn1.prototype);  // 函数默认添加的原型
console.log(fn1.constructor); // 构造器指向

5.png

  • 从上面我们可以看到:函数fn1的原型是Object(默认添加的,所以为空)
  • 函数的构造器是 Function
  • 当然,我们也可以手动给它添加原型属性和方法:
 function fn1(a , b){
  return a*b;
 }
// 手动添加原型属性和方法
// 通过 new 关键字可以继承
fn1.prototype.name = "小明";
fn1.prototype.age = 18;
fn1.prototype.fn = function(){
    console.log("年纪是:"+this.age);
}

1.2 原型链( __ proto __ )


定义:有限的实例对象和原型之间组成有限链,就是用来实现共享属性和继承的

从1.1中,我们知道了:所有的函数都是 Function 的实例。在构造函数上都有一个原型属性 prototype,该属性也是一个对象;那么在原型对象上有一个 constructor 属性,该属性指向的就是构造函数;

而实例对象上有一个 _ proto _ 属性,该属性也指向原型对象,并且该属性不是标准属性,不可以用在编程中,该属性用于浏览器内部使用(只用于看,不能操作)

image.png你需要理解这几句话:

  • 在函数里有一个属性prototype
  • 由该函数创建的对象默认会连接到该属性上
  • _proto_是站在对象角度来说的
  • prototype 是站在构造函数角度来说的
  • 如下图:


image.png

看图看不懂?代码来说话:

 function Person() {
 }
 Person.prototype.name = "养猪的王某人";
 Person.prototype.age = 18;
 Person.prototype.getAge = function () {
     console.log(this.age);
 }
 let person1 = new  Person();
 console.log(person1);  
 console.log(person1.name); // 继承父级的属性  
 console.log(person1.constructor);   

打印结果


image.png


图解:

image.pngperson1 通过 new关键字继承 Person(),可以得到原型链上的属性和方法


说的通俗一点,小明上学,自己挣不到钱,可以用住他爸妈的房子,花他们的钱,这就属于继承(血脉继承)。


函数内的构造器(constructor)指向构造他的函数


原型链的查找规则:


由下往上找,一层一层找,直到找到 null,如果找到 null还没找到,就报错。

寻找自身属性,如果找到了,就返回该值

如果没找到,继续往上找(逐级)

直到找到 null为止

如果还没找到,抛出错误

看了这么久,休息一会儿吧


image.png



3. this 指向问题


  • this 的指向在函数创建的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁。
  • 一般来说,谁调用,指向谁(并不是所有情况)
  • 最外层的 this (指向window)
<script>
  console.log(this) // window
</script>
  • 方法内的 this(依然指向window)
function a(){
  let userName = "张三";
  console.log(this) // window
}
a();  // 相当于window.a()
  • 对象内的 this(看调用情况)
let o = {
   userName: '张三',
   fn:function(){
       console.log(this.userName);
   }
}
o.fn(); // 打印结果:张三  o调用 this 指向o
  • 箭头函数本身没有作用域,所以 this 指向它的上级作用域
var id = 66;
function fn5(){
    setTimeout(()=>{
        console.log(this.id + '====id')
    },500)
}
fn5({id:21});
  • 箭头函数,this 指向定义时候的对象,fn5在window作用域下,所以this指向window;
  • 箭头函数的外层,fn5函数的this就是window
  • 箭头函数的this与它的执行没有关系,在定义的时候就决定了


目录
相关文章
|
2月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
70 2
|
2月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的医院综合管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的医院综合管理系统附带文章源码部署视频讲解等
35 5
|
2月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的宠物援助平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的宠物援助平台附带文章源码部署视频讲解等
63 4
|
2月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的宠物交易平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的宠物交易平台附带文章源码部署视频讲解等
55 4
|
2月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的大学生入伍人员管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的大学生入伍人员管理系统附带文章源码部署视频讲解等
67 4
|
2月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
71 3
|
2月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp宿舍管理系统的附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp宿舍管理系统的附带文章源码部署视频讲解等
62 3
|
2月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的家政平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的家政平台附带文章源码部署视频讲解等
44 3
|
1月前
|
JavaScript 前端开发
JS:一篇文章带你搞懂什么是异步
JS:一篇文章带你搞懂什么是异步