Web前端高频面试题解析(javascript篇)--- 每日十题(1)

目录

1.JS数据类型及它们的区别

基本数据类型:

引用数据类型:

区别:

2.基本数据类型中的null和undefined的区别和应用

1.区别

2.什么时候会有null

3.什么时候会出现undefined

3.数据类型检测的方式

1.typeof

2.instanceof

3.constructor

4. Object.prototype.toString.call( )

4.instanceof操作符的实现原理及实现

 手写instanceof的实现原理

5.typeof NaN的结果

6.判断数组的方法

1.通过Object.prototype.toString.call()做判断

2.通过原型链做判断

3.通过ES6的Array.isArray()做判断

4.通过instanceof做判断

5.通过Array.prototyps.isPrototypeOf做判断

7.+操作符什么时候用于字符串的拼接

8.Object.is和===,==的区别

1.==双等号:

2.===三等号:

3.Object.is( ):

9.如何处理隐式转换

1.ToPrimitive方法

如果值为基本类型,则直接返回值本身;

如果值为对象,其看起来大概是这样:

1.当type为number是规则如下:

2.当type为string是规则如下:

2.基本数据类型的隐式转换

1.+操作符

2.- , * , /  操作运算符

3.==操作符

4.<和>比较符

4.其他情况下,转换为数字再比较

3.针对对象的隐式转换

10.深拷贝和浅拷贝的区别

1.赋值:

2.浅拷贝:

1.手写浅拷贝

2.Object.assign(target, obj, ···)

3.Array.slice(Array.prototype.slice)和Array.concat(Array.prototype.concat)

3.深拷贝:

1.JSON.parse(JSON.stringify(obj))

2.扩展运算符(第一层)

3.函数库lodash的_.cloneDeep方法deep

4. 手写递归方法

5.循环引用


1.JS数据类型及它们的区别

总共有七种

基本数据类型:

string/number/boolean/null/undefined  /  symbol(ES6:创建之后独一无二,并且不可变的数据类型,为了解决全局变量冲突问题)

引用数据类型:

Object

区别:

1.声明变量时的存储分配

基本数据类型存储在栈中var a=10,

引用数据类型存储在堆中var a=[1,2,3,4],如果再去改变它数组在增加,大小也在变大a=[1,2,3,4,......],大小不固定,会随着长度在增大

2.不同的内存分配也带来了不同的访问机制;

不可以直接访问堆内存空间的位置以及直接操作堆内存空间,只能操作对象在栈内存中的引用地址;

基本数据类型时可以直接访问到,

引用数据类型访问引用地址,根据引用地址找到堆中实体。

3.复制变量时的不同;

基本数据类型:var a=1,var b=a ,将原始值的副本赋值新的变量;传值

引用数据类型:var obj={name:'zz'} ,var obj1=obj,将引用地址赋值给新的变量;传址

var a = [1, 2, 3, 4]
var b = a
var c = a[0]
console.log(b)//(4) [1, 2, 3, 4]
console.log(c)//1
// 改变数值
b[3] = 6
c = 7
console.log(a[3])//6
console.log(a[0])//1

Web前端高频面试题解析(javascript篇)--- 每日十题(1)_第1张图片

 Web前端高频面试题解析(javascript篇)--- 每日十题(1)_第2张图片

2.基本数据类型中的null和undefined的区别和应用

1.区别

都是代表没有值

  1. null表示“没有对象”,该处不应该有值
  2. undefined表示“缺少值”,该处应该有值,但是还没有定义
  3. 转为数值不同,null转为数值为0;undefined转为数值NaN(不是一个数字)
console.log(Number(null))//0
console.log(Number(undefined))//NaN

2.什么时候会有null

  1. 作为函数的参数,表示该函数的参数不是对象
  2. 作为对象原型链的终点
console.log(Object.getPrototypeOf(Object.prototype))//null,为null说明到达原型链的终点了

3.什么时候会出现undefined

  1. 变量被声明了,但是没有赋值,就等于undefined
  2. 调用函数时,应该提供的参数没有提供,该参数就等于undefined
  3. 对象没有赋值的属性,该属性的值为undefined
  4. 函数没有返回值时,默认返回undefined
var a
console.log(a)//1.undefined

// 这个a存在局部作用域,与上面的a不一样
function fun1 (a) {
   console.log(a)//2.undefined
}
fun1()

var obj = {}
console.log(obj.name)//3.undefined

var f = fun1()
console.log(f)//4.undefined

3.数据类型检测的方式

1.typeof

检测数据类型的运算符。

console.log("数值", typeof 10)//number
console.log("布尔", typeof true)//boolean
console.log("字符串", typeof '你好')//string
console.log("数组", typeof [])//object,因为它归为引用类型
console.log("函数", typeof function () { })//function
console.log("对象", typeof [])//object
console.log("undefined", typeof undefined)//undefined
console.log("null", typeof null)//object,
//浏览器的bug,因为所有的值在计算机中都是以二进制的编码来进行存储的,把前三位都为0的当作对象;null可以看作时空对象的指针,基本数据类型

2.instanceof

检测某一个实例是否属于这个类。可以正确判断对象的类型,不可以判断基本上就类型;内部运行机制,判断在它的原型链上能否找到这个类型的原型。

null和undefined不可

console.log("数值", 10 instanceof Number)//false
console.log("布尔", true instanceof Boolean)//false
console.log("字符串", '你好' instanceof String)//false
console.log("数组", [] instanceof Array)//true
console.log("函数", function () { } instanceof Function)//true
console.log("对象", {} instanceof Object)//true

3.constructor

检测实例和类的关系(根据实例和类的关系,从而检测数据类型)。constructor作为object的属性,这个属性就是引用原来构造该对象的函数。

null和undefined不可

console.log("数值", (10).constructor === Number)//true
console.log("布尔", (true).constructor === Boolean)//true
console.log("字符串", ('你好').constructor === String)//true
console.log("数组", ([]).constructor === Array)//true
console.log("函数", (function () { }).constructor === Function)//true
console.log("对象", ({}).constructor === Object)//true

4. Object.prototype.toString.call( )

检测数据类型。使用对象原型的toString的方法。

  1. 数组,函数是作为Object的实例,会重写toString方法。
  2. 原型上的toString是可以用来检测数据类型的;
  3. 而数组,函数会重写toString方法,
  4. 所以不同的对象在调用toString方法的时候会根据原型链,调用的是对应重写之后的toString方法,而不会调用Object原型上的方法。
var a = Object.prototype.toString

console.log("数值", a.call(10))//[object Number]
console.log("布尔", a.call(true))//[object Boolean]
console.log("字符串", a.call('你好'))//[object String]
console.log("数组", a.call([]))//[object Array]
console.log("函数", a.call(function () { }))//[object Function]
console.log("对象", a.call({}))//[object Object]

4.instanceof操作符的实现原理及实现

instanceof操作符用于判断构造函数的prototype属性是否出现在对象的原型链中的任何位置。

console.log({} instanceof Object)//true
// 构造函数也可以称为一类
// 1.定义构造函数
function Person (name) {
      this.name = name
}
// 2.实例化对象
let obj = new Person('张三')

// 1.obj的原型,看实例对象
console.log(Object.getPrototypeOf(obj))
//constructor: ƒ Person(name)说明两者有关系
// 2.构造函数的原型,看构造函数
console.log(Person.prototype)//Person自身有原型属性
// constructor: ƒ Person(name)
// 3.看两者是否有关系,是否相等
console.log(Object.getPrototypeOf(obj) === Person.prototype)//true
// 说明obj的原型是等于构造函数的原型属性,
// 构造函数的prototype属性出现在对象的原型链中

// Person是构造函数,也可以称为是一类,obj属于Person这一类
console.log(obj instanceof Person)//true

console.log(obj instanceof Object)//true
console.log(Person.prototype)//constructor: ƒ Person(name)
console.log(Object.prototype)//constructor: f Object(),object和Person原型属性两个不相等
console.log(Object.getPrototypeOf(Object.getPrototypeOf(obj)))//constructor: f Object(),两个相等
console.log(Object.getPrototypeOf(Object.getPrototypeOf(obj))===Object.prototype)//true
obj这一类也出现在了Object的原型链上面,只不过它的原型还要在往上在找一次

 手写instanceof的实现原理

  // 手写instanceof的实现原理
    // 1.定义一个函数,里面有两个行参left,right
    function instance (left, right) {
      // 2.获取对象(left)的原型
      let proto = Object.getPrototypeOf(left)
      // 3.(拿到right的属性)获取构造函数的prototype属性
      let prototype = right.prototype
      // 4.
      while (true) {
        // 4.判断原型能不能获取到,拿不到则返回false
        if (!proto) {
          return false
        }
        // 5.判断这个对象的原型跟构造函数的原型属性是否相等,如果相等则返回true
        if (proto === prototype) {
          return true
        }
        // 说明上面两个if都不成立,原型对象不等于构造函数的prototype属性。
        // 又获取proto的原型,再次赋值
        // 如果没有找到,就继续在原型链上找
        proto = Object.getPrototypeOf(proto)
        // 相当于console.log(Object.getPrototypeOf(Object.getPrototypeOf(obj)))
      }
    }

5.typeof NaN的结果

NaN(not a number)不是一个数字,表示是否属于number类型的一种状态:是或否,不是确切的值。

 JS中number数据类型除了浮点型和整数型,还有一个特殊值NaN

console.log(typeof NaN)//number
var a = "abc"
// 当一个表达式输出NaN,表达式中存在不可转化的变量,并且返回了无效的结果,不是返回确切的值
console.log(Number(a))//NaN

// 它与自身不相等,因为NaN不是一个确切的值,代表一个范围
console.log(NaN === NaN)//false
console.log(NaN !== NaN)//true

6.判断数组的方法

1.通过Object.prototype.toString.call()做判断

console.log(Object.prototype.toString.call([]))//[object Array]打印的是结果
// 如果想做判断的话,需要拿到字符串的关键信息Array
// 用到字符串截取slice(包头不包尾)
console.log(Object.prototype.toString.call([]).slice(8,-1)==='Array')//true

2.通过原型链做判断

如果对象的隐式原型等于构造函数的显式原型的话,那么说明这个对象就是这个构造函数实例化出来的。

// 对象隐式原型和构造函数的显式原型
console.log([].__proto__ === Array.prototype)//true

3.通过ES6的Array.isArray()做判断

console.log(Array.isArray([]))//true

4.通过instanceof做判断

跟原型链挂钩的。

console.log([] instanceof Array)//true

5.通过Array.prototyps.isPrototypeOf做判断

判断Array是否在传入内容的原型链上。

console.log(Array.prototype.isPrototypeOf([]))//true

7.+操作符什么时候用于字符串的拼接

如果+操作符其中一个操作数是字符串()(或者是通过ToPrimitive操作之后,最终得到的字符串),则执行字符串的拼接,否则执行数字加法。

var a = { name: 'zz' }//-->[object Object]
// a.valueOf()
// a.toString()
var b = { age: 11 }//-->[object Object]
// b.valueOf()
// b.toString()
var c = 1
console.log(a + c)//[object Object]1
console.log(a + b)//[object Object][object Object]

console.log(1 + 1 + '23')//2+'23'->223
console.log(1 + '23' + 4 + 1)//'123'+4+1->'1234'+1->12341

8.Object.is和===,==的区别

Object.is()和比较运算符“===”(严格相等运算符)和“==”(相等运算符)的区别

1.==双等号:

如果两边的类型不一致,进行强制类型转换,然后再去进行比较。

console.log(1 == true)//-->1==1-->true
console.log(1 == '1')//-->1==1-->true

2.===三等号:

如果两边类型不一致,不会进行强制类型转换,直接返回false-->等型等值。

console.log(1 === true)//false
console.log(1 === '1')//false
console.log(NaN === NaN)//false
console.log(+0 === -0)//true,隐式转换

3.Object.is( ):

判断两个值是否严格相等。处理一些特殊的情况,-0和+0不再相等,两个NaN是相等的。

console.log(Object.is(+0, -0))//false
console.log(Object.is(NaN, NaN))//true

9.如何处理隐式转换

1.ToPrimitive方法

ToPrimitive方法(转换为基本数据类型):这是JS中每个值隐含的自带的方法,用来将值(无论是基本类型值还是对象类型)转换为基本类型值。

如果值为基本类型,则直接返回值本身;

如果值为对象,其看起来大概是这样:

  1. ToPrimitive(obj,type)
  2. obj-->需要转换的对象
  3. type-->期望的结果类型
  4. type的值可以为number或者string,默认情况下为number

1.当type为number是规则如下:

(1)调用obj的valueOf方法(返回对象的原始值),如果为原始值,则返回,否则下一步

(2)再调用obj的toString方法(转换为字符串类型),如果为原始值,则返回,否则下一步

(3)抛出TypeError异常

2.当type为string是规则如下:

(1)调用obj的toString方法,如果为原始值,则返回,否则下一步

(2)再调用obj的valueOf方法,如果为原始值,则返回,否则下一步

(3)抛出TypeError异常

对象的data的type默认为string,其他的一般为number。

var objToNumber = function (value) {
      return Number(value.valueOf().toString())
}
console.log(objToNumber([]))//数组最后转为数字类型之后的结果-->0
console.log(objToNumber({}))//对象转为数字类型之后的结果-->通过toString转变为字符串[object Object]-->在通过Number去转换时,转换不了-->NaN

2.基本数据类型的隐式转换

把两边的值通过ToPrimitive方法转为基本数据类型,再进行操作。

1.+操作符

+的两边有至少一个string类型变量时,两边的变量都会被隐式转换为字符串;其他情况下两边的变量都会被转换为数字。

console.log(5 + '12')//512
console.log(1 + false)//1
console.log('1' + false)//1false
console.log(true + false)//1

2.- , * , /  操作运算符

会转换为数字

console.log(15 - '12')//3
console.log(1 * false)//0
console.log(1 / 'aa')//NaN

3.==操作符

会转为数字类型进行比较。

console.log(3 == true)//3-->3,true-->1,false
console.log('0' == false)//'0'-->0,false-->0,true
console.log('0' == '0')//true

4.<和>比较符

按照字母的排列顺序进行比较

console.log('c' > 'b')//true
console.log('de' < 'fg')//true

4.其他情况下,转换为数字再比较

console.log('12' < 13)//true
console.log(false < -1)//false

3.针对对象的隐式转换

var a = {}
console.log(a > 2)//false
console.log(a.valueOf())//{}
console.log(a.toString())//[object Object]
console.log(Number(a.toString()))//NaN
// 1.a.valueOf()-->对象
// 2.a.toString()-->字符串
// 3.Number()-->比较-->NaN
// 4.NaN跟2进行比较-->false

10.深拷贝和浅拷贝的区别

主要在于复制出来的新对象原来的对象是否会互相影响,改一个,另外一个也会发生改变。

深拷贝和浅拷贝只针对引用数据类型(Object、Array、Function、Date、RegExp等)。 

  1. 引用数据类型创建时开辟的是堆内存。
  2. 基本数据类型创建时开辟的是栈内容。
  3. 为自动分配的内存空间,它由系统自动释放;是动态分配的内存,大小不一定会自动释放。 

1.赋值:

指向同一个存储空间,无论哪个对象发生改变,都是改变同一存储空间的内容。把一个对象赋值给一个新的变量时,赋值是该对象的在栈中的地址,而不是堆中的数据

var obj = { name: 'zz', age: 10, children: { name: 'ls', age: 18 } }
var obj1 = obj
obj1.age = 20
obj1.children.name = '第二层的名字'
console.log(obj)//{name: 'zz', age: 20}
console.log(obj1)//{name: 'zz', age: 20}
一层二层都改变

Web前端高频面试题解析(javascript篇)--- 每日十题(1)_第3张图片

2.浅拷贝:

仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅拷贝出来的对象也会相应的改变。对对象的各个属性进行依次复制,只拷贝对象的第一层属性。

  • 它会创建一个新对象,对原有对象的成员进行依次拷贝。 
  • 对于目标对象第一层为基本数据类型的数据,就是直接赋值(基本数据类型的值),即「传值」。
  • 而对于目标对象第一层为引用数据类型的数据,就是直接赋存于栈内存中的堆内存地址,即「传址」。
  • 因此如果两个对象指向同一个地址,修改其中一个对象的属性,则另一个对象的属性也会改变。(无新栈)

 新对象跟旧对象共享内存,修改其中一个,另一个也会受到影响 

1.手写浅拷贝

// 手写浅拷贝
    function shallowCopy (obj) {
      //根据object的类型判断是新建一个数组还是对象
      let newObj = Array.isArray(obj) ? [] : {}
      // 如果不是对象直接返回,只拷贝对象
      if (typeof obj !== "object") return
      //遍历object,并判断是object的属性,然后拷贝
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          newObj[key] = obj[key]
        }
      }
      return newObj
    }
    var obj = { age: 10, children: { sex: 'nv' } }
    var obj1 = shallowCopy(obj)
    obj1.age = 20
    obj1.children.sex = '第二层的性别'
    console.log(obj)//{age: 10, children: {…}}
    console.log(obj1)//{age: 20, children: {…}}

Web前端高频面试题解析(javascript篇)--- 每日十题(1)_第4张图片

2.Object.assign(target, obj, ···)

target:目标对象,obj是源对象。源对象自身的任意多个的可枚举属性拷贝给目标对象,然后返回目标对象。

  1. 对象合并:将源对象里面的属性添加到目标对象中去。

  2. 若两者的属性名(有同名属性)有冲突,后面的将会覆盖前面的。

  3. 只有一个参数,当参数为对象时,直接返回该对象;当参数不是对象时,先将参数转为对象,然后返回。

  4. null 和 undefined 不能转化为对象,所以第一个参数不能为null或 undefined。
  5. 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
  6. Array和Object都适用

1.修改obj1.age,改变对象中的基本类型值

花括号{}:目标对象,obj、obj1是源对象。

原对象未改变(当object只有一层的时候,是深拷贝 )

// 2.Onject.assigin()方法
var obj = { name: 'zz', age: 10 }
var obj1 = Object.assign({}, obj)
obj1.age = 20
console.log(obj)//{name: 'zz', age: 10}
console.log(obj1)//{name: 'zz', age: 20}

2.修改obj1.age,改变对象中的引用类型值

原对象也被改变

var obj = { name: 'zz', age: { zz: 10 } }
var obj1 = Object.assign({}, obj)
obj1.age.zz = 20
console.log(obj)//{name: 'zz', age: 20}
console.log(obj1)//{name: 'zz', age: 20}

Web前端高频面试题解析(javascript篇)--- 每日十题(1)_第5张图片

3.Array.slice(Array.prototype.slice)和Array.concat(Array.prototype.concat)

Array.slice和Array.contact方法都不会修改原数组,而是会返回一个对原数组进行浅拷贝的新数组。

  1. 这两种方法同Object.assign()一样,都是对第一层属性依次拷贝。
  2. 如果第一层的属性是基本数据类型,直接拷贝值;如果是引用数据类型,就拷贝内存地址。
  3. 是Array原型上的方法,只适用于Array

 1.Array.slice

  1.  slice():从已有数组中返回选定的元素。
  2. 该方法有两个参数(可选);两个参数都不写,则实现数组的浅拷贝(arr.slice(start, end)包头不包尾)。 
var obj = [1, 3, [4, 5]]
var obj1 = obj.slice()
//改变基本类型值,不会改变原数组
obj1[1] = 7
//改变数组中的引用类型值,原数组也会跟着改变
obj1[2][0] = 7
console.log(obj)//[1, 3, [7, 5]]
console.log(obj1)//[1, 7, [7, 5]]
console.log(obj1 === obj)//false

2.Array.concat

  1. concat() :用于合并两个或多个数组。
  2. 该方法有两个参数(可选);两个参数都不写,则实现数组的浅拷贝。
var obj = [1, 3, { sex: 'nv' }]
var obj1 = obj.concat()
obj1[2].sex = 'nan'
console.log(obj)//[1, 3, {sex: 'nan'}]
console.log(obj1)//[1, 3, {sex: 'nan'}]
console.log(obj1 === obj)//false

var obj = [1, 3, [4, 5]]
var obj1 = obj.concat()
//改变基本类型值,不会改变原数组
obj1[1] = 7
//改变数组中的引用类型值,原数组也会跟着改变
obj1[2][0] = 7
console.log(obj)//[1, 3, [7, 5]]
console.log(obj1)//[1, 7, [7, 5]]
console.log(obj1 === obj)//false

Web前端高频面试题解析(javascript篇)--- 每日十题(1)_第6张图片

3.深拷贝:

在内存中开辟一块新的地址用于存放复制的对象。递归拷贝对象的所有属性。

  1. 是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。 
  2. 对属性中所有引用类型的值,遍历到是基本类型的值为止。

新对象跟旧对象不会共享内存,修改其中一个,另一个不会受影响

1.JSON.parse(JSON.stringify(obj))

用JSON.stringify:将js的值(对象或者数组)转为一个JSON字符串(序列化),再用JSON.parse()(反序列化)把字符串解析成对象Object类型。

// JSON.stringify:将js的值(对象或者数组)转为一个JSON字符串
// JSON.parse:用来解析JSON字符串,转换为Object类型
var obj = { a: 0, b: { c: 2 } }
var obj1 = JSON.parse(JSON.stringify(obj))
obj1.a = 7
obj1.b.c = 7
console.log(obj)//{a: 0, b: {c: 2}}
console.log(obj1)//{a: 7, b: {c: 7}}

Web前端高频面试题解析(javascript篇)--- 每日十题(1)_第7张图片

缺点: 这种方法虽然可以实现数组或对象深拷贝,但不能处理函数。(JSON.stringify():将一个javascript值转换我一个JSON字符串,不能接受函数。)

  1. 如果对象中有时间对象,那么用该方法拷贝之后的对象中,时间是字符串形式,而不是时间对象。
  2. 如果对象中有RegExp、Error对象-->序列化的结果是空对象。
  3. 如果对象中有函数、undefined-->序列化的结果会把函数或undefined丢失。
  4. 如果对象中有NaN、infinity、-infinity-->序列化的结果会变成null。
  5. 只能是序列化对象的可枚举自有属性,如果对象中有是构造函数生成的,那么拷贝后会丢弃对象的constructor。(深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。)
  6. 如果对象中存在循环引用的情况无法正确实现深拷贝。

2.扩展运算符(第一层)

当object只有一层的时候,是深拷贝,第二层的拷贝还是浅拷贝

var obj = {
      name: 'zz',
      age: {
        zz: 10
      }
}
var obj1 = { ...obj }
obj1.name = 'nm'
obj1.age.zz = 7
console.log(obj)//{name: 'zz', age: {age: {zz: 7}}}
console.log(obj1)//{name: 'nm', age: {age: {zz: 7}}}

Web前端高频面试题解析(javascript篇)--- 每日十题(1)_第8张图片

3.函数库lodash的_.cloneDeep方法deep

https://www.lodashjs.com/docs/lodash.cloneDeep#_clonedeepvalue

这个方法类似_.clone,除了它会递归拷贝 value。(注:也叫深拷贝)。

var _ = require('lodash');
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);// => false

4. 手写递归方法

遍历对象、数组直到都是基本数据类型。再去复制,就是深度拷贝。

  1. obj.hasOwnProperty(prop)判断obj这个对象中是否含有prop这个属性,返回布尔值,有则true,没有则false

法一: 

  function deepCopy (obj) {
      var newObject = Array.isArray(obj) ? [] : {}
      if (obj && typeof (obj) === 'object') {
        for (let key in obj) {
          if (obj.hasOwnProperty(key)) { 
            if (obj[key] && typeof (obj[key]) === 'object') {
              newObject[key] = deepCopy(obj[key])
            } else {
              newObject[key] = obj[key]
            }
          }
        }
      }
      return newObject
    }

法二: 

function deepCopy (object) {
//  处理数组
 let newObject = Array.isArray(object) ? [] : {}
 //  判断是否是引用类型。不是,直接返回
 if (!object || typeof object !== "object") return
 for (let key in object) {
   if (object.hasOwnProperty(key)) {
     newObject[key] = typeof object[key] === "object" ? deepCopy(object[key]) : object[key]
        }
   }
   return newObject
}

测试数据: 

var obj1 = {
      a: 1,
      b: {
        c: 2
      }
}
var obj2 = deepCopy(obj1)
obj2.a = '7'
obj2.b.c = '7'
console.log('obj1', obj1)
console.log('obj2', obj2)

 Web前端高频面试题解析(javascript篇)--- 每日十题(1)_第9张图片

5.循环引用

父级引用:当对象的某个属性,正是这个对象本身(两个互相引用的对象),可能会出现子元素->父对象->子元素...死循环,导致栈溢出。

解决办法:需要判断一个对象的字段是否引用了这个对象或这个对象的任意父级(在遍历的时候判断是否互相引用)

var obj1 = {
      a: 1,
      b: {
        c: 2
      }
}
var obj2 = deepCopy(obj1)
obj2.z = obj2
console.log('obj1', obj1)
console.log('obj2', obj2)

Web前端高频面试题解析(javascript篇)--- 每日十题(1)_第10张图片

function deepCopy (obj, parent = null) {
      // 创建一个新对象
      var newObject = Array.isArray(obj) ? [] : {}
      let _parent = parent
      // 该字段有父级则需要追溯该字段的父级
      while (_parent) {
        // 如果该字段引用了它的父级,则为循环引用
        if (_parent.originalParent === obj) {
          // 循环引用返回同级的新对象
          return _parent.currentParent
        }
        _parent = _parent.parent
      }
      // 如果obj是一个新对象
      if (obj && typeof (obj) === 'object') {
        for (let key in obj) {
          // 如果字段的值也是一个对象
          if (obj[key] && typeof (obj[key]) === 'object') {
            result[i] = deepCopy(obj[i], {
              //递归执行深拷贝,将同级的待拷贝对象与新对象传递给parent,方便追溯循环引用
              originalParent: obj,
              currentParent: result,
              parent: parent
            })
          } else {
            newObject[key] = obj[key]
          }
        }
      }
      return newObject
    }

最后看不懂了,先到这里吧

参考文章

https://www.jb51.net/article/140928.htm

https://www.jb51.net/article/181898.htm

https://www.jb51.net/article/192518.htm

 发现宝藏前端面试题,等我学成归来

前端面试题之Vue篇 · 语雀

你可能感兴趣的:(前端,javascript,ecmascript,es6)