一些常见的JavaScript笔试题汇总,后续不断更新。。。

334 阅读9分钟

§ 实现一个函数 test word 转成 Test Word

const str1 = "test word";
const strTo1 = (str1) => {
  return str1
    .toLowerCase()
    .replace(/( |^)[a-z]/g, (item) => item.toUpperCase());
};
strTo1(str1); // Test Word

§ 实现一个函数 font-size 转成 fontSize

// 2.实现一个函数 font-size 转成 fontSize
const str2 = "font-size";
const strTo2 = (str2) => {
  let reg = /-(\w)/g;
  str2 = str2.replace(reg, ($0, $1) => {
    return $1.toUpperCase();
  });
  return str2;
};
strTo2(str2); // fontSize

§ 实现一个函数 输入fooBarTest,输出foo-bar-tes

function tuoTo(targetString) {
  return targetString.replace(/([A-Z])/g, function (match) {
    return "-" + match.toLowerCase();
  });
}
console.log(tuoTo("fooBarTest")); // foo-bar-tes

§ 封装一个方法,实现金额格式化,输入10000000000,输出10,000,000,000

const money = '10000000000';
const result = money.replace(/(?=\B(\d{3})+$)/g, ",");
console.log(result); // 10,000,000,000

§ 封装一个chunk方法,传参chunk(['a', 'b', 'c', 'd'], 2)实现[['a', 'b'], ['c', 'd']],chunk(['a', 'b', 'c', 'd'], 3)实现[['a', 'b', 'c'], ['d']]

function chunk(arr, num) {
  let len = arr.length;
  if (!len || !num || num < 1) {
    return [];
  }
  let index = 0;
  let resIndex = 0;
  let result = new Array(Math.ceil(len / num));
  while (index < len) {
    result[resIndex++] = arr.slice(index, (index += num));
  }
  return result;
}
console.log(chunk(["a", "b", "c", "d"], 2)); // [['a', 'b'], ['c', 'd']]
console.log(chunk(["a", "b", "c", "d"], 3)); // [['a', 'b', 'c'], ['d']]

§ 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标

const twoSum = (nums, target) => {
  let arr = [];
  for (let i = 0; i < nums.length; i++) {
    let num = target - nums[i];
    let index = nums.lastIndexOf(num);
    if (index != i && index != -1) {
      arr.push(i, index);
      return arr;
    }
  }
};
twoSum([2, 7, 11, 15], 9); // [0,1]

§ 判断当前data是否为数组

const arr = []

Array.isArray(arr) // true

Object.prototype.toString.call(arr) === '[object Array]' // true

arr.constructor === Array // true

arr instanceof Array // true

§ 判断当前data是否为对象

let obj = {}

Object.prototype.toString.call(obj) === '[Object Object]'

obj.constructor === Object

Object.getPrototypeOf(obj) === Object.prototype

§ 如何判断当前对象是否为空

推荐使用:Reflect.ownKeys()

// 我们写几种常见的方法
function isEmpty01(obj) {
    return JSON.stringify(obj) === '{}'
}

function isEmpty02(obj) {
    return Object.keys(obj).length === 0
}

function isEmpty03(obj) {
    return Object.getOwnPropertyNames(obj).length === 0
}

function isEmpty04(obj) {
    let flag = true
    for(let key in obj){
        if(key){
            flag = false
            break
        }
    }
    return flag
}

// 测试
let obj = {
    a: 123
}
console.log(isEmpty01(obj)) // false
console.log(isEmpty02(obj)) // false
console.log(isEmpty03(obj)) // false
console.log(isEmpty04(obj)) // false

但是有一个问题,如果对象中是Symbol类型的值,就判断不出来,所以推荐用 Reflect.ownKeys()

const key = Symbol('123')
let obj = {
    [key]: 1
}
const isEmptyOfObj = (obj) => {
    return Reflect.ownKeys(obj).length === 0
}
isEmptyOfObj(obj) // false

§ 判断当前对象中是否存在某个属性

// in
// in 方法有个缺点,如果属性来自对象的原型,它仍然会返回 true
const obj = {name:"aaa",age:"bbb"}
'age' in obj // true

// Reflect.has
Reflect.has(obj, 'age') // true

// obj.hasOwnProperty
obj.hasOwnProperty('age') // true

// Object.prototype.hasOwnProperty.call
Object.prototype.hasOwnProperty.call(obj, 'age') // true

// Object.hasOwn
Object.hasOwn(obj, 'age') // true

§ 如何判断当前是否为NaN

NaN(Not a Number)有一个非常特殊的特性,NaN不等于其本身,也不等于任何

isNaN:先尝试转换为数字,如果隐式转换为Number类型失败,就会返回NaN

Numnber.isNaN():

NaN == NaN // false

NaN === NaN // false

需要注意的是:NaN与任何值相加,结果都等于NaN

console.log(NaN + 1) // NaN

console.log(NaN + null) // NaN

console.log(NaN + undefined) // NaN

通过NaN的特性,我们封装一个方法:

const is_NaN = (x) => {
    return x != x
}

// 测试
is_NaN(NaN) // true

is_NaN('a' - 100) // true

is_NaN('a100') // true

is_NaN('a' + 100) // true

is_NaN(100) // false

is_NaN('100') // false

还可以使用es6中的isNaN()来判断

isNaN(NaN) // true

isNaN('a' - 100) // true

isNaN('a100') // true

isNaN('a' + 100) // true

isNaN(100) // false

isNaN('100') // false

当一个表达式中有减号,乘号,除号运算符的时候,JS引擎在计算之前会试图将表达式的每一项都转化为Number类型,如果转换失败,就会返回NaN

100 - 'abc' // NaN

'abc' * 100 // NaN

'abc' / 100 // NaN

Number('abc') // NaN

+'100abc' // NaN

§ 求数组交集,并集,差集

const arr1 = [4,9,5,9]
const arr2 = [9,4,9,8,4]

// 交集
const res1 = Array.from(new Set(arr1.filter(item => new Set(arr2).has(item)))) // [4, 9]
// 并集
const res2 = Array.from(new Set(arr1.concat(arr2))) // [4, 9, 5, 8]
// 差集
const res3 = Array.from(new Set(arr1.concat(arr2).filter(
    item => !new Set(arr1).has(item) || !new Set(arr2).has(item)
))) // [5, 8]

更简单的写法:

const arr1 = [4,9,5,9]
const arr2 = [9,4,9,8,4]

// 并集
const union = [...new Set([...arr1,...arr2])] // [4, 9, 5, 8]

// 交集
const cross = [...new Set(arr1.filter((item) => arr2.includes(item)))] // [4, 9]

// 差集
const diff = union.filter((item) => !cross.includes(item)) // [5, 8]

§ 封装一个返回data数据类型的方法

const Type = {}
for(let i = 0,type;type = ['String','Number','Boolean','Null','Undefined','Array','Object'][i++];){
  ((type) => {
    Type['is' + type] = (obj) => {
      return Object.prototype.toString.call(obj) === `[object ${type}]`
    }
  })(type)
}
console.log(Type.isString('hello'))

§ 数组合并去重

let arr01 = [1, 2, 3, 4, 5, 5, 6, 6, 7];
let arr02 = [2, 3, 4, 4, 5, 6, 8, 8, 9, 10, 11];
const mergeArr = (arr01, arr02) => {
  let i = 0,
    j = 0;
  let res = [];
  while (i < arr01.length && j < arr02.length) {
    if (arr01[i] < arr02[j]) {
      if (res.length === 0 || res[res.length - 1] !== arr01[i]) {
        res.push(arr01[i]);
      }
      i++;
    } else {
      if (res.length === 0 || res[res.length - 1] !== arr02[j]) {
        res.push(arr02[j]);
      }
      j++;
    }
  }
  while (i < arr01.length) {
    if (res.length === 0 || res[res.length - 1] !== arr01[i]) {
      res.push(arr01[i]);
    }
    i++;
  }
  while (j < arr02.length) {
    if (res.length === 0 || res[res.length - 1] !== arr02[j]) {
      res.push(arr02[j]);
    }
    j++;
  }
  return res;
};
mergeArr(arr01, arr02); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
let arr001 = [1, 2, 3, 4, 5, 5, 6, 6, 7];
let arr002 = [2, 3, 4, 4, 5, 6, 8, 8, 9, 10, 11];
let arrCon = arr001.concat(arr002)
let arrRes = new Set(arrCon)
console.log(arrRes)

§ 数组对象合并去重

// new Map
let arr = [
  { id: 1, name: "AAAA" },
  { id: 2, name: "BBBB" },
];
let arr1 = [
  { id: 1, name: "AAAA" },
  { id: 3, name: "CCCC" },
];
let arrs = [...arr, ...arr1];
let map = new Map();
for (let item of arrs) {
  if (!map.has(item.id)) {
    map.set(item.id, item);
  }
}
let newArr = [...map.values()];

§ 对象合并去重

let obj01 = [
  { id: 1, name: "AAAA" },
  { id: 2, name: "BBBB" },
];
let obj02 = [
  { id: 1, name: "AAAA" },
  { id: 3, name: "CCCC" },
];
const objMerge = (obj01, obj02) => {
  let arrs = [...obj01, ...obj02];
  let map = new Map();
  for (let item of arrs) {
    if (!map.has(item.id)) {
      map.set(item.id, item);
    }
  }
  return [...map.values()];
};
objMerge(obj01, obj02);
/*
[
  {"id": 1,"name": "AAAA"},
  {"id": 2,"name": "BBBB"},
  {"id": 3,"name": "CCCC"}
]
*/

§ 数组合并

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

// 1
arr1.concat(arr2);

// 2
for (let i in arr2) {
  arr1.push(arr2[i]);
}

// 3
arr1.push.apply(arr1, arr2);

// 4
let newArr = [];
newArr = [...arr1, ...arr2];

§ 数组去重

let arrList = [ 1, 2, 2, "abc", "abc", true, true, false, false, undefined, undefined, NaN, NaN ];

// 1.利用Set()+Array.from()
const removeArr = (arr) => {
  return Array.from(new Set(arr));
};
removeArr(arrList); // [1, 2, 'abc', true, false, undefined, NaN]

// 2.利用数组的indexOf方法
const removeArr2 = (arr) => {
  let newArr = [];
  arr.map((item) => {
    if (newArr.indexOf(item) === -1) {
      newArr.push(item);
    }
  });
  return newArr;
};
removeArr2(arrList); // [1, 2, 'abc', true, false, undefined, NaN]

// 3.利用数组的includes方法
const removeArr3 = (arr) => {
  let newArr = [];
  arr.map((item) => {
    if (!newArr.includes(item)) {
      newArr.push(item);
    }
  });
  return newArr;
};
removeArr3(arrList); // [1, 2, 'abc', true, false, undefined, NaN]

// 4.利用map()
const removeArr4 = (arr) => {
  let map = new Map();
  let newArr = [];
  arr.forEach((item) => {
    if (!map.has(item)) {
      map.set(item, true);
      newArr.push(item);
    }
  });
  return newArr;
};
removeArr4(arrList); // [1, 2, 'abc', true, false, undefined, NaN]

// 5.利用对象
const removeArr5 = (arr) => {
  let obj = {};
  arr.map((item) => {
    if (!obj[item]) {
      obj[item] = true;
    }
  });
  return Object.keys(obj);
};
removeArr5(arrList); // [1, 2, 'abc', true, false, undefined, NaN]

// 6.利用双层循环+是金子的splice方法
const removeArr6 = (arr) => {
  let len = arr.length;
  for (let i = 0; i < len; i++) {
    for (let j = i + 1; j < len; j++) {
      if (arr[i] == arr[j]) {
        arr.splice(j, 1);
        len--;
      }
    }
  }
  return arr;
};
removeArr6(arrList); // [1, 2, 'abc', true, false, undefined, NaN]

§ 数组去重,两个属性相同的对象也认为是重复的

字节面试题

function uniqueArray(arr) {
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    const item1 = arr[i];
    let isFind = false;
    for (let j = 0; i < result.length; j++) {
      const item2 = result[j];
      if (equals(item1, item2)) {
        isFind = true;
        break;
      }
    }
    if (!isFind) {
      result.push(item1);
    }
  }
  return result;
}
// 判断是否为原始值
function isPrimitive(value) {
  return value === null || !["object", "function"].includes(typeof value);
}
// 判断两个值是否一样
function equals(value1, value2) {
  if (isPrimitive(value1) || isPrimitive(value2)) {
    return Object.is(value1, value2);
  }
  const entries1 = Object.entries(value1);
  const entries2 = Object.entries(value2);
  if (entries1.length !== entries2.length) {
    return false;
  }
  for (const [key, value] of entries1) {
    if (!equals(value, value2[key])) {
      return false;
    }
  }
  return true;
}
// 测试
console.log(equals({ a: 1, b: 2 }, { b: 2, a: 1 })); // true
console.log(equals({ a: 1, b: 2, c: 3 }, { b: 2, a: 1 })); // false

§ 对象去重

let resources = [
  { name: "AAA", age: "18" },
  { name: "AAA", age: "18" },
  { name: "BBB", age: "20" },
  { name: "AAA", age: "18" },
  { name: "CCC", age: "19" },
  { name: "CCC", age: "19" },
];
const resourcesFn = (resources) => {
  let temp = {};
  resources = resources.reduce((prev, curv) => {
    if (temp[curv.name]) {
    } else {
      temp[curv.name] = true;
      prev.push(curv);
    }
    return prev;
  }, []);
  return resources;
};
resourcesFn(resources);
/*
[
  {"name": "AAA", "age": "18"},
  {"name": "BBB", "age": "20"},
  {"name": "CCC", "age": "19"}
]
*/

§ 数组排序

let arrSort = [32, 34, 1, 6, 45, 54, 3, 7, 5, 4];

// 1.sort快速排序
arrSort.sort((a, b) => a - b); // [1, 3, 4, 5, 6, 7, 32, 34, 45, 54]
arrSort.sort((a, b) => b - a); // [54, 45, 34, 32, 7, 6, 5, 4, 3, 1]

// 冒泡排序
const sortArr = (arr) => {
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length; j++) {
      if (arr[j] > arr[j + 1]) {
        const ele = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = ele;
      }
    }
  }
};
sortArr(arrSort);
console.log(arrSort); // [1, 3, 4, 5, 6, 7, 32, 34, 45, 54]

// 插入排序
const insertSort = (arr) => {
  let newArr = [arr[0]];
  for (let i = 1; i < arr.length; i++) {
    newArr.push(arr[i]);
    for (let j = 0; j < newArr.length - 1; j++) {
      if (newArr[newArr.length - j - 1] < newArr[newArr.length - j - 2]) {
        let arrItem = newArr[newArr.length - j - 2];
        newArr[newArr.length - j - 2] = newArr[mewArr.length - j - 1];
        newArr[newArr.length - h - 1] = arrItem;
      }
    }
  }
  return newArr;
};

§ 对象排序

let objSort = [
  { name: "lk", age: 23 },
  { name: "zy", age: 15 },
  { name: "ij", age: 27 },
  { name: "er", age: 13 },
];

objSort.sort((a, b) => {
  return a.age - b.age;
});
console.log(objSort);

// 根据name排序
objSort.sort((a, b) => {
  if (a.name > b.name) {
    return 1;
  } else {
    return -1;
  }
});
console.log(objSort);

§ 数组扁平化

const array = [1, { a: 2 }, [3, 4, [5, 6], 7], 8, 9]
const aaa = (array) => {
    return array.reduce((a,b) => {
        return a.concat(
            Array.isArray(b) ? aaa(b) : 
            Object.prototype.toString.call(b) === '[object Object]' ? 
            Object.values(b) : b
        )
    },[])
}
console.log(aaa(array))
let arrNum = [1, 2, [3, 4, 5, [6]], 7, 8, [9], 0];

arrNum.toString().split(",").map(Number); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

// 1.flat
console.log(arrNum.flat(Infinity)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

// 2.reduce
const flatten = (arr) => {
  return arr.reduce((a, b) => {
    if (Array.isArray(b)) {
      return a.concat(flatten(b));
    } else {
      return a.concat(b);
    }
  }, []);
};
console.log(flatten(arrNum)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

// 3.扩展运算符
const flatten02 = (arr) => {
  return arr.reduce((a, b) => {
    if (Array.isArray(b)) {
      return [...a, ...flatten02(b)];
    } else {
      return [...a, b];
    }
  }, []);
};
console.log(flatten02(arrNum)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

// 4.非递归(栈)
const flatten03 = (arr) => {
  const result = [];
  const stack = [...arr];
  while (stack.length) {
    const next = stack.pop();
    if (Array.isArray(next)) {
      stack.push(...next);
    } else {
      result.unshift(next);
    }
  }
  return result;
};
console.log(flatten03(arrNum)); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

§ 数组根据字典排序

sort()排序默认是根据编码排序的

const city = ['上海','北京','杭州','广东','深圳','西安']
city.sort((a,b) => a.localeCompare(b))
// ['北京', '广东', '杭州', '上海', '深圳', '西安']

§ 实现一个深拷贝

window.structuredClone()

const obj = {
    name: '123',
    arr: [1, 10, '123', { aa: 'aaa' }],
    data: {
        name: 'abc',
        age: 20,
    },
    field: null,
    ceshi: undefined,
}
obj.other = obj;

const copy = window.structuredClone(obj)
console.log(copy)

JS逻辑

let obj = {
  aaa: "zzz",
  bbb: {
    ccc: "xxx",
    ddd: {
      eee: "yyy",
      fff: "ttt",
    },
  },
};

function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  const clone = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    clone[key] = deepClone(obj[key]);
  }
  return clone;
}
let cloneData = deepClone(obj);
console.log("原数据", obj);
console.log("克隆数据", cloneData);

这样写存在一个问题,如果克隆的对象带有引用的时候,递归就会无限循环,为了解决这个问题,重新写一个更完美的深拷贝方法:

// 测试
const obj = {
    name: '123',
    arr: [1, 10, '123', { aa: 'aaa' }],
    data: {
        name: 'abc',
        age: 20
    },
    field: null,
    ceshi: undefined
}
obj.other = obj
obj.arr.push(obj)
// 深拷贝
function deepClone(obj) {
    // 当对象克隆的时候,对每个对象进行缓存,下次克隆的时候,直接读取缓存,来解决对象引用无限递归
    // weakMap不影响垃圾回收,如果使用map,则会提升内存泄漏风险
    const cache = new WeakMap()
    // 声明子函数,避免全局变量污染
    function _deepClone(obj) {
        // 判断当前传参是否为非对象,如果非对象,则直接返回结果
        if (obj === null || typeof obj !== 'object') {
            return obj
        }
        // 如果当前传参是对象,则判断缓存中是否存在数据,如果存在,则直接读取缓存
        if(cache.has(obj)) {
            return cache.get(obj)
        }
        // 判断当前传过来的是数组还是对象
        const result = Array.isArray(obj) ? [] : {}
        // 如果缓存中不存在,则把对象加进去
        cache.set(obj, result)
        // 循环对象并且用递归的方式对对象的每个属性进行深度克隆
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                result[key] = _deepClone(obj[key])
            }
        }
        return result
    }
    return _deepClone(obj)
}
const cloneObj = deepClone(obj)
console.log(cloneObj)

§ ['a','b','c'] 数组全排列

// 递归回溯
function rank(nums) {
  let arr = [];
  function recursion(unusedNum, usedNum) {
    if (usedNum.length == nums.length) {
      arr.push(usedNum);
      return;
    }
    for (let i = 0; i < unusedNum.length; i++) {
      if (unusedNum[i] != null) {
        let temp = [...unusedNum];
        temp[i] = null;
        recursion(temp, [...usedNum, unusedNum[i]]);
      }
    }
  }
  recursion(nums, []);
  return arr;
}
rank(['a','b','c'])
/*
[ 
    [ "a", "b", "c" ],
    [ "a", "c", "b" ],
    [ "b", "a", "c" ],
    [ "b", "c", "a" ],
    [ "c", "a", "b" ],
    [ "c", "b", "a" ]
]
*/

§ [2,2,2,2,3,3,4,4,4,2,2,3,5],数组中元素重复的次数,返回[[2,4],[3,2],[4,3],[2,2],[3,1],[5,1]]

§ [2,2,2,2,3,3,4,4,4,2,2,3,5],数组中元素连续重复的次数最多的元素

let arr = [2, 2, 2, 2, 3, 3, 4, 4, 4, 2, 2, 3, 5];
function maxArr(arr) {
  let num = 1;
  let max = 1;
  let maxItem = null;
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === arr[i + 1]) {
      num = num + 1;
      if (max < num) {
        max = num;
        maxItem = arr[i];
      }
    } else {
      num = 1;
    }
  }
  return [maxItem, max];
}
console.log(maxArr(arr)); // [2, 4]

§ [2,2,2,2,3,3,4,4,4,2,2,3,5],数组中元素重复的次数最多的元素

function findNum(a) {
  let res = [0, 0]; //定义一个数组,存放结果
  for (let i = 0; i < a.length; i++) {
    for (j = 0, count = 0; j < a.length; j++) {
      if (a[i] == a[j]) {
        ++count; //找到一个重复的项,计数器就 +1
      }
    }
    if (count > res[0]) {
      res[0] = count;
      res[1] = a[i];
    } else if (count == res[0] && res[1] < a[i]) {
      res[1] = a[i];
    }
  }
  return res;
}
findNum([2, 2, 2, 2, 3, 3, 4, 4, 4, 2, 2, 3, 5]); // [6, 2]

§ 统计数组中重复出现过的元素 [1, 2, 4, 4, 3, 3, 1, 5, 3] -> [1, 3, 4]

function repeatArr(arr) {
  let newArr = [];
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] == arr[j]) {
        newArr.push(arr[j]);
      }
    }
  }
  return Array.from(new Set(newArr));
}
repeatArr([1, 2, 4, 4, 3, 3, 1, 5, 3]); // [1, 3, 4]

§ 封装一个函数,传入'abcdefgh' 返回 'abc-def-gh'

const chunkString = (str) => {
    return str.replace(/(.{3})(?=.)/g, '$1-')
}
const res = chunkString("abcdefgh") // "abc-def-gh"

§ 实现一个版本号的排序,['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5'] => ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']

const versionList = (data) => {
  data.sort((a, b) => {
    let i = 0;
    const arr1 = a.split(".");
    const arr2 = b.split(".");
    while (true) {
      const s1 = arr1[i];
      const s2 = arr2[i];
      i++;
      if (s1 === undefined || s2 === undefined) {
        return arr2.length - arr1.length;
      }
      if (s1 === s2) continue;
      return s2 - s1;
    }
  });
  return data;
};
versionList(['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5'])
// ['4.3.5', '4.3.4.5', '4.2', '2.3.3', '0.302.1', '0.1.1']

实现一个版本号的对比,如果新版本小于老版本,则返回-1,如果新版本大于老版本,则返回1,如果老版本等于新版本,则返回1

const version = (version1,version2) => {
  let ver1 = version1.split(".").map(item => parseInt(item))
  let ver2 = version2.split(".").map(item => parseInt(item))
  for(let i = 0;i < ver1.length && i < ver2.length;i++){
    if(ver1[i] < ver2[i]) return -1;
    if(ver1[i] > ver2[i]) return 1;
  }
  if(ver1.length < ver2.length) return -1;
  if(ver1.length > ver2.length) return 1;
  return 0;
}
const res = version("1.0.1","1.0.2")
console.log(res)

§ 实现封装一个柯里化函数,实现自由累加得出结果

function add() {
  const args = [].slice.call(arguments);
  const _add = function(){
    if (arguments.length === 0) {
      return args.reduce((a, b) => a + b);
    } else {
      [].push.apply(args, arguments);
      return _add;
    }
  };
  return _add;
}
// 测试
add(1, 2, 3)() // 6
add(1,2,3)(1)(2)(3)(4,5,6)(7,8)() // 42

§ 实现一个防抖函数和一个节流函数

// 防抖
const debounce = (fn, delay = 300) => {
  let timer;
  return function () {
    const args = arguments;
    if (timer) {
      clearInterval(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this.args);
    }, delay);
  };
};
// 测试
window.addEventListener(
  "scroll",
  debounce(() => {
    console.log(123);
  }, 1000)
);
// 节流
const throttle = (fn, delay) => {
  let flag = true;
  return () => {
    if (!flag) return;
    flag = false;
    timer = setTimeout(() => {
      fn();
      flg = true;
    }, delay);
  };
};
// 测试
window.addEventListener(
  "scroll",
  throttle(() => {
    console.log(123);
  }, 1000)
);

§ 实现一个promise

  • Promise 是一个类,传入的参数是一个执行器,会立即执行

  • Promise 有三种状态

    • pending 等待
    • fulfilled 完成
    • rejected 失败
  • 状态只能改变一次,不能逆向。

    • pending -> fulfilled
    • pending -> rejected
  • Promise 使用 resolve 和 reject 两个函数来改变状态

  • then 方法内部做状态判断,执行对应的方法

  • 有静态方法 Promise.resolve 和 Promise.reject

// 状态常量
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class Promise {
  state = PENDING;
  value = undefined;
  reason = undefined;
  onResolveCallback = [];
  onRejectCallback = [];
  constructor(executor) {
    try {
      executor(this.resolve, this.reject);
    } catch (err) {
      this.reject(err);
    }
  }
  resolve(data) {
    if (this.state !== PENDING) {
      return;
    }
    this.state = FULFILLED;
    this.value = data;
    while (this.onResolveCallback.length > 0) {
      const currentResolve = this.onResolveCallback.shift();
      currentResolve(this.value);
    }
  }
  reject(err) {
    if (this.state !== PENDING) {
      return;
    }
    this.state = REJECTED;
    this.reason = err;
    while (this.onRejectCallback.length > 0) {
      const currentReject = this.onRejectCallback.shift();
      currentReject(this.reason);
    }
  }
  static resolve(param) {
    // param是Promise对象,直接返回
    if (param instanceof Promise) {
      return param;
    }
    // 转成普通的Promise
    return new Promise((resolve) => {
      resolve(param);
    });
  }
  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason);
    });
  }
  catch(fn) {
    if (this.state == REJECTED) {
      typeof fn == "function" && fn(this.reason);
    }
  }
  then(resolve, reject) {
    const realResolve =
      typeof resolve == "function" ? resolve : (value) => value;
    const realReject =
      typeof reject == "function"
        ? reject
        : (reason) => {
            throw reason;
          };
    // 链式调用,需要返回新的Promise实例
    const newPromise = new Promise((resolve, reject) => {
      // 创建一个微任务,等待Promise初始化完成
      const microResolve = () => {
        queueMicrotask(() => {
          try {
            const x = realResolve(this.value);
            resolvePromise(newPromise, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      };
      const microReject = () => {
        queueMicrotask(() => {
          try {
            const x = realReject(this.reason);
            resolvePromise(newPromise, x, reasolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      };
      if (this.state == FULFILLED) {
        return microResolve(this.value);
      }
      if (this.state == REJECTED) {
        return microReject(this.reason);
      }
      if (this.state == PENDING) {
        this.onResolveCallback.push(microResolve);
        this.onRejectCallback.push(microReject);
      }
    });
    return newPromise;
  }
}
function resolvePromise(newPromise, x, resolve, reject) {
  if (newPromise == x) {
    return reject(new Error("循环引用"));
  }
  if (x instanceof Promise) {
    x.then(resolve, reject);
  } else {
    resolve(x);
  }
}

§ 手写一个轮播图,let obj = {a:{b:1}},给定一个obj与一个'a.b'返回1,如果给了'd.b',则返回null

let a = {
  b: {
    c: {
      d: 1,
    },
  },
};
const getObj = (str) => {
  let arr = str.split(".");
  arr.shift();
  for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
    if (a[arr[i]]) {
      a = a[arr[i]];
    } else {
      return null;
    }
  }
  return a;
};
let val = getObj("a.b.c.d");
console.log(val);

§ 实现一个reduce函数

Array.prototype.Reduce = function (callback) {
  const initVal = arguments[1] ? arguments[1] : "";
  const len = this.length;
  if (!len && !initVal) {
  }
  if (!len) return initVal;
  let total = initVal ? initVal : this[0];
  let i = initVal ? 0 : 1;
  for (; i < len; i++) {
    total = callback(total, this[i], i, this);
  }
  return total;
};
// 测试
const testArr = [1, 2, 3, 4, 5];
const totalNum = testArr.Reduce((total, item, index, testArr) => {
  console.log(total, item, index, testArr);
  return total + item;
});
console.log(totalNum); // 15

§ 实现一个map函数

Array.prototype.Map = function (callback) {
  const result = [];
  const context = arguments[1] || window;
  if (typeof callback === "function") {
    for (let i = 0; i < this.length; i++) {
      result.push(callback.call(context, this[i], i, this));
    }
  } else {
    throw new Error("--");
  }
  return result;
};
// 测试
const testArr2 = [1, 2, 3, 4, 5];
const totalNum2 = testArr2.Map((item) => {
  return item * 2;
});
console.log(totalNum2); // [2, 4, 6, 8, 10]

§ 实现一个filter函数

Array.prototype.Filter = function (callback) {
  const result = [];
  const context = arguments[1] || window;
  if (typeof callback === "function") {
    for (let i = 0; i < this.length; i++) {
      let condition = callback.call(context, this[i], i, this);
      if (condition) {
        result.push(this[i]);
      }
    }
  } else {
    throw new Error("--");
  }
  return result;
};
// 测试
const testArr3 = [1, 2, 3, 4, 5];
const r = testArr3.Filter((item) => item === 3);
console.log(r); // [3]

§ 实现一个some函数

Array.prototype.Some = function (callback) {
  const result = [];
  const context = arguments[1] || window;
  if (typeof callback === "function") {
    for (let i = 0; i < this.length; i++) {
      let condition = callback.call(context, this[i], i, this);
      if (condition) {
        return true;
      }
    }
    return false;
  } else {
    throw new Error("--");
  }
  return result;
};
// 测试
const testArr4 = [1, 3, 5, 7, 9];
const result4 = testArr4.Some((item, index, testArr4) => item % 2 === 0); // false

§ 实现一个every函数

Array.prototype.Every = function (callback) {
  const result = [];
  const context = arguments[1] || window;
  if (typeof callback === "function") {
    for (let i = 0; i < this.length; i++) {
      let condition = callback.call(context, this[i], i, this);
      if (!condition) {
        return false;
      }
    }
    return true;
  } else {
    throw new Error("--");
  }
  return result;
};
// 测试
const testArr5 = [1, 3, 5, 7, 9];
const result5 = testArr5.Every((item, index, testArr4) => item >= 10);
console.log(result5); // false

§ 实现一个forEach函数

Array.prototype.ForEach = function (callback) {
  const context = arguments[1] || window;
  if (typeof callback === "function") {
    for (let i = 0; i < this.length; i++) {
      callback.call(context, this[i], i.this);
    }
  } else {
    throw new Error("--");
  }
};
const test5obj = [
  {
    name: "aaa",
    age: 12,
  },
  {
    name: "bbb",
    age: 13,
  },
];
test5obj.ForEach((item) => {
  console.log(item);
});

§ 遍历递归树结构,每个node节点增加一个属性

const objects = [
  {
    name:"aaa"
  },{
    name:"bbb",
    children:[
      {
        name:"ccc"
      },
      {
        name:"eee",
        children:[
          {
            name:"fff"
          }
        ]
      }
    ]
  }
]
const addName1 = (data,preLast) => {
  data.forEach((item) => {
    console.log(preLast)
    item[Object.keys(preLast)] = Object.values(preLast)[0]
    if(item.children){
      addName1(item.children,preLast)
    }
  })
}
addName1(objects,{age:"123"})
console.log(objects)

§ 遍历递归树结构,在其中一个属性后面加一个后缀

const objects = [
  {
    name:"aaa"
  },{
    name:"bbb",
    children:[
      {
        name:"ccc"
      },
      {
        name:"eee",
        children:[
          {
            name:"fff"
          }
        ]
      }
    ]
  }
]
const addName1 = (data,preLast) => {
  data.forEach((item) => {
    item.name = item.name + preLast
    if(item.children){
      addName1(item.children,preLast)
    }
  })
}
addName1(objects,'--zzz')
console.log(objects)

§ 浏览器运行 foo 函数,是否会导致栈溢出?

function foo {
    setTimeout(foo, 0)
}

答案是不会栈溢出。首先导致栈溢出的原因是在一个函数中无限的调用自身,就会导致栈溢出,因为每次调用函数都会在栈里面执行上下文,会占用栈的一部分空间,上下文是让函数执行结束之后才会被弹出去,如果在函数里面调用自身,就会导致当前函数还没有运行结束的时候,又在调用自己,这样就会一直累计,然而栈的总大小是有限的,这样就会撑爆,所以会导致栈溢出

在这道题当中之所以不会栈溢出是因为,函数调用自己是用异步来操作的

§ 如何让下面的判断成立

if (a == 1 && a == 2 && a == 3) {
  console.log("成立");
}

等号运算符规则:

  1. 两端类型相同,比较值
  2. 两端存在NaN,返回false
  3. undefined 和 null只有与自身比较,或者互相比较时,才会返回true
  4. 两端都是原始类型,转换成数字比较
  5. 一端是原始类型,一段是对象类型,把对象类型转换成原属类型吼进入第4步

对象如何转原始类型?

  1. 如果对象拥有[Symbol.toPrimitive]方法,调用该方法,如果该方法能得到原始值,使用该原始值,如果得不到原始值,则抛出异常
  2. 调用对象的valueOf方法,如果该方法能得到原始值,则使用该原始值,如果得不到原始值,则进入下一步(对象中默认有valueOf方法)
  3. 调用对象的toString()方法,如果该方法能得到原始值,则使用该原始值,如果得不到原始值,则抛出异常

所以这道题的解法是:

const a = {
  const: 1,
  valueOf() {
    return this.const++;
  },
};
if (a == 1 && a == 2 && a == 3) {
  console.log("成立");
}