JavaScript 面试题

深拷贝

JSON.parse(JSON.stringnify(object)) 的不足:

  • 会忽略 undefined
  • 会忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象

自己实现一个深拷贝

function deepCopy(obj) {
  let newObj
  if (typeof obj === 'object') { // 数组或对象 typeof 会返回'object'
    newObj = Array.isArray(obj) ? [] : {}
    for(let key in obj) {
      if (obj.hasOwnProperty(key)) {
        // 如果对象的属性仍然为对象/数组则递归
        newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key]
      }
    }
  } else {
    newObj = obj // 简单数据类型直接复制
  }
  return newObj
}

测试一下

let a = {
  age: undefined,
  sex: Symbol('male'),
  jobs: function () {},
  name: 'cmk'
}

let b = JSON.parse(JSON.stringify(a))
console.log(b) // { name: 'cmk' }
let c = deepCopy(a)
console.log(c) // { age: undefined, sex: Symbol(male), jobs: [Function: jobs], name: 'cmk' }

数组扁平化 5 种方法

const arr = [1, [2, 3, [4, 5]]]
function flattern(arr) {
  return arr.reduce((preValue, currValue, currIndex, array) => {
    return preValue.concat(Array.isArray(currValue) ? flattern(currValue) : currValue)
  }, []) // []作为第一个preValue
}

// toString & split
function flattern2(arr) {
  return arr.toString().split(',').map(item => {
    return Number(item) // split分隔后数组元素为字符串形式, 需要转换为数值形式
  })
}
PS:该方法及下一种方法都有一个问题,即[1, '1', 2, '2']的结果是[1, 1, 2, 2],我们改变了原数据(类型),这是不应该的
// join & split
function flattern3(arr) {
  return arr.join(',').split(',').map(item => { // join方法和toString方法效果一样?
    return parseInt(item)
  })
}

// concat递归
function flattern4(arr) {
  let res = []
  arr.map(item => {
    if (Array.isArray(item)) {
      res = res.concat(flattern(item))
    } else {
      res.push(item)
    }
  })
  return res
}

// 扩展运算符
function flattern5(arr) {
  while (arr.some(item => Array.isArray(item))) { // 如果数组元素中有数组
    arr = [].concat(...arr) // [].concat(...[1, 2, 3, [4, 5]]) 扩展运算符可以将二维数组变为一维的
  }
  return arr
}

如果只是将二维数组扁平化为一维数组的话也可以这样写

function flattern(arr) {
  return Array.prototype.concat.apply([], arr)
}

函数柯里化

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。

简单来说,思路就是参数不够的话就存储参数并返回一个函数准备接收新的参数,参数够了的话就执行。

function curry(fn, args1 = []) { // 默认值设为[],防止访问args1.length报错
  return fn.length === args1.length ? fn.apply(null, args1) : function (...args2) {
    return curry(fn, args1.concat(args2))
  }
}

// fn(a,b,c,d)=>fn(a)(b)(c)(d)
// fn(a,b,c,d)=>fn(a, b)(c)(d)
function add(a, b, c, d) {
  console.log(a + b + c + d)
}

let curryAdd = curry(add)
curryAdd(1, 2)(3)(4) // 10
curryAdd(1)(2)(3)(4)
curryAdd(1, 2, 3, 4)

另一种常见的问题

/* 
    实现一个add方法,使计算结果能够满足如下预期:
    add(1)(2)(3) = 6;
    add(1, 2, 3)(4) = 10;
    add(1)(2)(3)(4)(5) = 15; 
*/

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存 _args 并收集所有的参数值
    var _adder = function() {
        _args.push(...arguments);
        return _adder;
    };

    // 利用 toString 隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }
    return _adder;
}

add(1)(2)(3)                // 6
add(1, 2, 3)(4)             // 10
add(1)(2)(3)(4)(5)          // 15
add(2, 6)(1)                // 9
posted @ 2019-08-13 18:04  freedom雲  阅读(128)  评论(0编辑  收藏  举报