JavaScript基础知识点整理(二)——运算符比较、闭包、深浅拷贝、原型、

内容主要涉及JavaScript:运算符比较、闭包、深浅拷贝、原型


1、== 和 === 的差异

对于==而言,若对比双方的类型不一样的话,则存在类型转换,这也就用到了。

JavaScript类型转换:包括显式转换和隐式转换

当需要对比两个变量ab是否相同,需要如下流程:

  • 首先判定两者类型是否相同,相同则比较大小。
  • 类型不同的话,则进行类型参数转换。
  • 判定是否对比nullundefined,是的话就返回true
  • 判定两者类型是否stringnumber,是的话就将字符串转换为number
1 == '1'1 ==  1
  • 判定其中的一方是否boolean,是的话就boolean转换为number,并且进行重写进行判定的流程。
'1' == true'1' ==  11  ==  1
  • 判定其中一方是否object,并且另一方是stringnumber,是的话就将object转换为原始类型进行判定,也就是执行x.tostring以及valueof
'1' == { name: 'yck' }'1' == '[object Object]'
  • 返回false

综述,如下流程图:
在这里插入图片描述


2、JavaScript闭包

首先给出闭包的正确定义:

设定一个函数能访问外部的变量,那么就形成了一个闭包,而且不是一定要返回一个函数

给出下述代码:

let a = 1
// 产生闭包
function fn() {
  console.log(a);
}

function fn1() {
  let a = 1
  // 产生闭包
  return () => {
    console.log(a);
  }
}
const fn2 = fn1()
fn2()

接下来阐述数据存放的正确规则:局部、占用空间确定的数据,一般存放于栈中,否则就在堆中;
在这里插入图片描述
上图能看到一个内部的对象scopes,这个对象就是一般所说的作用域链。
根据作用域链寻找的顺序,包含了闭包、全局对象。因此我们能够通过闭包访问本次销毁的变量,所以原始数据一般存放在栈上。

另外最开始对于闭包的定位是:假如一个函数能够访问外部的变量,则形成了闭包。给出如下代码:

let a = 1
var b = 2
// 形成闭包
function fn() {
  console.log(a, b);
}

在这里插入图片描述

可以得出结论,若是var则直接被挂载到global上,若是其他的关键字声明则被挂载到Script上面,虽然这些数据同样还是存在于scopes,但是全局变量在内存中是存放在静态区域的,因为全局变量无需参与到GC。

最后总结一下原始类型存储位置:局部变量被存储在栈上,全局变量存储在静态区域上,其它都存储在堆上。


3、JavaScript深浅拷贝

3.1、浅拷贝

首先可以通过Object.assign来解决这个问题,这个函数会拷贝所有的属性数值到新的对象中,若属性是对象的话,就拷贝地址

let a = {
  age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1

也可以利用...来实现浅拷贝:

let a = {
  age: 1
}
let b = { ...a }
a.age = 2
console.log(b.age) // 1

通常而言浅拷贝能够解决大部分的问题,若遇到下面的情况需要利用深拷贝:

let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = { ...a }
a.jobs.first = 'native'
console.log(b.jobs.first) // native

3.2、深拷贝

深拷贝通常可以利用JSON.parse(JSON.stringify(object))来解决,这个方式能够解决大部分的情况:

let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

不过该方法存在的问题:

let obj = {
  a: 1,
  b: {
    c: 2,
    d: 3,
  },
}
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)

若对象存在循环引用,则报如下错误:

在这里插入图片描述
同时遇到不支持的数据类型,例如函数、undefined、symbol等等,这些属性都会被忽略掉:

let a = {
  age: undefined,
  sex: Symbol('male'),
  jobs: function() {},
  name: 'yck'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "yck"}

给出JSON支持的数据类型:
在这里插入图片描述

若需要拷贝的对象含有内置类型并且不包含函数,则可以利用MessageChannel

function structuralClone(obj) {
  return new Promise(resolve => {
    const { port1, port2 } = new MessageChannel()
    port2.onmessage = ev => resolve(ev.data)
    port1.postMessage(obj)
  })
}

var obj = {
  a: 1,
  b: {
    c: 2
  }
}

obj.b.d = obj.b

// 注意该方法是异步的
// 可以处理 undefined 和循环引用对象
const test = async () => {
  const clone = await structuralClone(obj)
  console.log(clone)
}
test()

同时我们也可以自己实现一个深拷贝:

// 利用 WeakMap 解决循环引用
let map = new WeakMap()
function deepClone(obj) {
  if (obj instanceof Object) {
    if (map.has(obj)) {
      return map.get(obj)
    }
    let newObj
    if (obj instanceof Array) {
      newObj = []     
    } else if (obj instanceof Function) {
      newObj = function() {
        return obj.apply(this, arguments)
      }
    } else if (obj instanceof RegExp) {
      // 拼接正则
      newobj = new RegExp(obj.source, obj.flags)
    } else if (obj instanceof Date) {
      newobj = new Date(obj)
    } else {
      newObj = {}
    }
    // 克隆一份对象出来
    let desc = Object.getOwnPropertyDescriptors(obj)
    let clone = Object.create(Object.getPrototypeOf(obj), desc)
    map.set(obj, clone)
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        newObj[key] = deepClone(obj[key])
      }
    }
    return newObj
  }
  return obj
}

不过上述代码存在缺陷,递归可能存在栈溢出,这是因为执行栈的大小有限,可以通过遍历的方式改写递归,例如JavaScript实现层序遍历BFS,也可以利用数组模拟栈解决栈溢出的问题。


4、JavaScript原型

当创建一个对象时,let obj = { age: 25 },我们可以发现能使用很多种函数,但是没有定义过他们,如下图:
在这里插入图片描述

在浏览器中打印obj会发现存在一个__proto__的属性,这是因为每一个JavaScript对象都有一个__proto__的属性,这个属性指向了原型 ,这个属性目前不推荐使用,这只是浏览器早起为了访问内部属性prototype来实现的东西;
在这里插入图片描述
原型也是一个对象,并且该对象包含了很多的函数,所以推出结论:

对于obj而言,可以通过__proto__找到一个原型对象,再该对象中定义了很多的函数让我们使用。

同时还可以发现一个constructor的属性,也就是构造函数:
在这里插入图片描述

打开constructor属性可以发现其中还有一个prototype属性,并且该属性和之前的__prototype__中的一模一样,因此给出如下的结论:

原型的constructor属性指向了构造函数,构造函数通过prototype来指向原型,不过并不是所有的函数都具有这个属性,Function.prototype.bind() 就没有这个属性

给出如下的图来总结记忆:
在这里插入图片描述
原型链就是多个对象通过__proto__方式连接起来了,总结具体如下:

  • object是所有对象的父亲,所有对象都可以通过__proto__找到它。
  • Funciton是所有函数的父亲,所有函数都可以通过__proto__找到它。
  • 函数的prototype是一个对象
  • 对象的__proto__属性指向了原型,__proto__把对象和原型连接起来组成了原型链。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值