js基础和常考面试题(下)|小册免费学

199 阅读4分钟

强等于和弱等于

也就是说=====的区别

个人理解是强等于要求比较双方要一模一样,弱等于比较时类型不一致时会转换成一致的进行比对

弱等于

比较规则:

  • 类型相同则判断大小
  • 双方为nullundefined,返回true
  • 双方为字符和数字,字符转成数字来判断
  • 一方为bool,则转成数字判断,true转成1,false为0
  • 其中一方是否为 object 且另一方为 string、number 或者 symbol,对象转成'[object Object]'进行判断,除非对象重写了toString和valueOf方法,不然就是这个了

image.png

image.png 更多:felix-kling.de/js-loose-co…

  • []==![]为何是true 1.的优先级高,![]false,规则为:null、undefined、NaN以及空字符串('')取反都为true,其余都为false

2.一方为bool,则转成数字,所以右方为0

3.一方为对象,一方是数字,所以要把左方转成数字,没有valueOf方法,用toString转成了''

4.字符和数字对比,字符转成数字,''又可以转成0

5.0==0=》true

  • {} == !{} {}.toString() -> NaNNaN不等于0,所以gg

闭包

简单定义:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。

意义:让我们可以间接访问函数内部的变量

复杂定义:在js中,函数里变量的作用域属于函数作用域,在函数执行后作用域会被清除,内存也会被回收,但是由于闭包是建立在一个函数内部的子函数,由于作用域链的关系,子函数可以访问上级作用域,所以即使上级函数执行完,作用域也不会销毁,这时的子函数,便有了访问上级作用域中变量的权限。

作用:封装私有变量,存储缓存变量,循环中创建闭包避免取到意外的值

优点:避免全局变量的污染 缺点:增加内存占用

特性:1.函数嵌套函数 2.子函数能访问外部变量 3.变量不会被垃圾回收机制回收

经典题

//循环中使用闭包解决 `var` 定义函数的问题
for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}

由于setTimeout异步,循环同步,输出的时候i为6个6

解决:

1.闭包
for (var i = 1; i <= 5; i++) {
  ;(function(j) {
    setTimeout(function timer() {
      console.log(j)
    }, j * 1000)
  })(i)
}
2.使用 setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入
for (var i = 1; i <= 5; i++) {
  setTimeout(
    function timer(j) {
      console.log(j)
    },
    i * 1000,
    i
  )
}
3.let
for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i)
  }, i * 1000)
}

深浅拷贝

如果两个变量引用同个对象,其中一个做出改变另外一个也会跟着变

浅拷贝

如果属性值是对象的话,拷贝的是地址,适合要拷贝的对象没有对象属性的情况

let a = {
  age: 1
}
let b = Object.assign({}, a)
let b = { ...a }

深拷贝

通常可以通过 JSON.parse(JSON.stringify(object)) 来解决

缺点:

忽略undefined,函数,symbol

RegExp,map,set引用丢失=>{},

时间对象会变成字符串

bigInt和循环引用会报错,

NaN和infinity会变成null,、

属性是构造函数会丢失对象的constructor,

segmentfault.com/a/119000002…

如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用 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()

自己实现的两种简易版深拷贝:

function deep(obj){
	var result = typeof obj.splice == 'function'?[]:{},key
	if(typeof obj  == 'object'){
		for(key in obj){
			if(obj[key]&&typeof obj[key] ==='object')result[key]=deep(obj[key])
			else result[key]=obj[key]
		}
		//return result
	}
	return obj
}


function deepClone(obj) {
  function isObject(o) {
    return (typeof o === 'object' || typeof o === 'function') && o !== null
  }

  if (!isObject(obj)) {
    throw new Error('非对象')
  }

  let isArray = Array.isArray(obj)
  let newObj = isArray ? [...obj] : { ...obj }
  Reflect.ownKeys(newObj).forEach(key => {
    newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
  })

  return newObj
}

推荐使用lodash里的深拷贝

原型

每个对象都有自己的原型对象,并从原型对象上继承属性和方法。原型里有很多对象属性,有valueOf、toString等对象都有的方法,其中原型的constructor指向构造函数,构造函数的prototype又指向_proto_,__proto__指向上一层原型,

但是并不是所有函数都具有这个属性,Function.prototype.bind() 就没有这个属性。这个我试了一下,有返回值,Object反而才没有,懂的人有吗?

原型链:读取实例属性时,如果找不到,就会去原型里找,找不到就去原型里的原型里找,最终找到Functiion,Object上。创建的实例对象,其构造原型被改,所有基于这个构造函数创建的实例都会被改。这原型主要就是用来继承,以及用这个创建的实例对象可以访问到原型上的方法而不用每个实例对象都自己开辟个空间去创建方法。

另一种简单的说法:

原型链就是多个对象通过 proto 的方式连接了起来。为什么 obj 可以访问到 valueOf 函数,就是因为 obj 通过原型链找到了 valueOf 函数。

原型详细的:juejin.cn/post/684490…

github.com/KieSun/Drea…

image.png

  • 总结:

  • Object 是所有对象的爸爸,所有对象都可以通过 proto 找到它

  • Function 是所有函数的爸爸,所有函数都可以通过 proto 找到它

  • 函数的 prototype 是一个对象

  • 对象的 proto 属性指向原型, proto 将对象和原型连接起来组成了原型链

  • 本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情