LOADING

代码题

2024/9/12

写一个函数判断变量类型

function getType(data) {
  let type = typeof data;
  if (type !== "object") {
    return type;
  }
  return Object.prototype.toString.call(data).slice(8, -1);
}
console.log(getType(1)); // number
console.log(getType(true)); // boolean
console.log(getType([1, 2, 3])); // Array
console.log(getType(/abc/)); // RegExp
console.log(getType(new Date())); // Date
console.log(getType(new Person())); // Object
function Person() {}
console.log(getType({})); // Object

如何判断数组或对象

  1. 通过 instanceof 进行判断
var arr = [1,2,3,1];
console.log(arr instanceof Array) // true
  1. 通过对象的 constructor 属性
var arr = [1,2,3,1];
console.log(arr.constructor === Array) // true
  1. Object.prototype.toString.call(arr)
console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
console.log(Object.prototype.toString.call([]));//[object Array]
  1. 可以通过 ES6 新提供的方法 Array.isArray( )
Array.isArray([]) //true

数组去重

// 数字或字符串数组去重,效率高
function unique(arr) {
  var result = {}; // 利用对象属性名的唯一性来保证不重复
  for (var i = 0; i < arr.length; i++) {
    if (!result[arr[i]]) {
      result[arr[i]] = true;
    }
  }

  return Object.keys(result); // 获取对象所有属性名的数组
}

// 利用ES6的Set去重,适配范围广,效率一般,书写简单
function unique(arr) {
  return [...new Set(arr)];
}

// 任意数组去重,适配范围广,效率低
function unique(arr) {
  var result = []; // 结果数组
  for (var i = 0; i < arr.length; i++) {
    if (!result.includes(arr[i])) {
      result.push(arr[i]);
    }
  }
  return result;
}

实现一个函数,对一个 url 进行请求,失败就再次请求,超过最大次数就走失败回调,任何一次成功都走成功回调

function request(url, maxCount = 5) {
  return fetch(url).catch((err) => {
    if (maxCount > 0) {
      request(url, maxCount--);
    } else {
      Promise.reject(err);
    }
  });
}
request("www.baidu.com", 5).then((resp) => console.log(resp));

冒泡排序

function bSort(arr) {
  let len = arr.length;
  // 外层 for 循环控制冒泡的次数
  for (let i = 0; i < len - 1; i++) {
    for (let j = 0; j < len - 1 - i; j++) {
      // 内层 for 循环控制每一次冒泡需要比较的次数
      // 因为之后每一次冒泡的两两比较次数会越来越少,所以 -i
      if (arr[j] > arr[j + 1]) {
        let temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
  return arr;
}

//举个数组
myArr = [20, -1, 27, -7, 35];
//使用函数
console.log(bSort(myArr)); // [ -7, -1, 20, 27, 35 ]

实现一个 sleep 函数

function sleep(delay) {
  var start = new Date().getTime();
  while ((new Date().getTime() - start < delay) {
    continue;
  }
}

function test() {
  console.log('111');
  sleep(2000);
  console.log('222');
}

test()

这种实现方式是利用一个伪死循环阻塞主线程。因为 JS 是单线程的。所以通过这种方式可以实现真正意义上的 sleep。

给定两个数组,求并集,交集,差集

const arr1 = [];
const arr2 = [];

//并集
const union = [...new Set([...arr1, ...arr2])];

//交集
const cross = [...new Set(arr.filter((it) => arr2.includes(it)))];

//差集
const diff = union.fliter((it) => !cross.includes(it));

实现一个字符串匹配算法,从长度为 n 的字符串 S 中,查找是否存在字符串 T,T 的长度是 m,若存在返回所在位置。

// 完全不用 API
var getIndexOf = function (s, t) {
  let n = s.length;
  let m = t.length;
  if (!n || !m || n < m) return -1;
  for (let i = 0; i < n; i++) {
    let j = 0;
    let k = i;
    if (s[k] === t[j]) {
      k++;
      j++;
      while (k < n && j < m) {
        if (s[k] !== t[j]) break;
        else {
          k++;
          j++;
        }
      }
      if (j === m) return i;
    }
  }
  return -1;
};

// 测试
console.log(getIndexOf("Hello World", "rl"));

使用 JavaScript Proxy 实现简单的数据绑定

<body>
  <input type="text" id="model">
  <p id="word"></p>
</body>

<script>
  const model = document.getElementById("model")
  const word = document.getElementById("word")
  var obj= {};

  const newObj = new Proxy(obj, {
    get: function(target, key, receiver) {
      console.log(`getting ${key}!`);
      return Reflect.get(target, key, receiver);
    },
    set: function(target, key, value, receiver) {
      console.log('setting',target, key, value, receiver);
      if (key === "text") {
        model.value = value;
        word.innerHTML = value;
      }
      return Reflect.set(target, key, value, receiver);
    }
  });

  model.addEventListener("keyup",function(e){
    newObj.text = e.target.value
  })
</script>

打印出 1~10000 以内的对称数

function isSymmetryNum(start, end) {
  for (var i = start; i < end + 1; i++) {
    var iInversionNumber = +i.toString().split("").reverse().join("");
    if (iInversionNumber === i && i > 10) {
      console.log(i);
    }
  }
}
isSymmetryNum(1, 10000);

使用递归完成 1 到 100 的累加

function add(x, y) {
  if (x === y) {
    return x;
  } else {
    return y + add(x, y - 1);
  }
}

console.log(add(1, 100));

产生一个不重复的随机数组

// 生成随机数
function randomNumBoth(Min, Max) {
  var Range = Max - Min;
  var Rand = Math.random();
  var num = Min + Math.round(Rand * Range); //四舍五入
  return num;
}
// 生成数组
function randomArr(len, min, max) {
  if (max - min < len) {
    //可生成数的范围小于数组长度
    return null;
  }
  var hash = [];

  while (hash.length < len) {
    var num = randomNumBoth(min, max);

    if (hash.indexOf(num) == -1) {
      hash.push(num);
    }
  }
  return hash;
}
// 测试
console.log(randomArr(10, 1, 100));

在上面的代码中,我们封装了一个 randomArr 方法来生成这个不重复的随机数组,该方法接收三个参数,len、min 和 max,分别表示数组的长度、最小值和最大值。randomNumBoth 方法用来生成随机数。

给你一个数组,计算每个数出现的次数,如果每个数组返回的数都是独一无二的就返回 true 相反则返回的 false

输入:arr = [1,2,2,1,1,3]

输出:true

解释:在该数组中,1 出现了 3 次,2 出现了 2 次,3 只出现了 1 次。没有两个数的出现次数相同。

代码示例:

function uniqueOccurrences(arr) {
    let uniqueArr = [...new Set(arr)]
    let countArr = []
    for (let i = 0; i < uniqueArr.length; i++) {
        countArr.push(arr.filter(item => item == uniqueArr[i]).length)
    }
    return countArr.length == new Set(countArr).size
};

// 测试
console.log(uniqueOccurrences([1, 2, 2, 1, 1, 3])); // true
console.log(uniqueOccurrences([1, 2, 2, 1, 1, 3, 2])); // false

封装一个能够统计重复的字符的函数,例如 aaabbbdddddfff 转化为 3a3b5d3f

function compression(str) {
    if (str.length == 0) {
        return 0;
    }
    var len = str.length;
    var str2 = "";
    var i = 0;
    var num = 1;
    while (i < len) {
        if (str.charAt(i) == str.charAt(i + 1)) {
            num++;
        } else {
            str2 += num;
            str2 += str.charAt(i);
            num = 1;
        }
        i++;
    }
    return str2;
}
// 测试:
console.log(compression('aaabbbdddddfff')); // 3a3b5d3f

实现 5.add(3).sub(2)

这里想要实现的是链式操作,那么我们可以考虑在 Number 类型的原型上添加 add 和 sub 方法,这两个方法返回新的数

Number.prototype.add = function (number) {
    if (typeof number !== 'number') {
        throw new Error('请输入数字~');
    }
    return this.valueOf() + number;
};
Number.prototype.minus = function (number) {
    if (typeof number !== 'number') {
        throw new Error('请输入数字~');
    }
    return this.valueOf() - number;
};
console.log((5).add(3).minus(2)); // 6

请实现一个模块 math,支持链式调用 math.add(2,4).minus(3).times(2);

class Math {
    constructor(value) {
        let hasInitValue = true;
        if (value === undefined) {
            value = NaN;
            hasInitValue = false;
        }
        Object.defineProperties(this, {
            value: {
                enumerable: true,
                value: value,
            },
            hasInitValue: {
                enumerable: false,
                value: hasInitValue,
            },
        });
    }

    add(...args) {
        const init = this.hasInitValue ? this.value : args.shift();
        const value = args.reduce((pv, cv) => pv + cv, init);
        return new Math(value);
    }

    minus(...args) {
        const init = this.hasInitValue ? this.value : args.shift();
        const value = args.reduce((pv, cv) => pv - cv, init);
        return new Math(value);
    }

    times(...args) {
        const init = this.hasInitValue ? this.value : args.shift();
        const value = args.reduce((pv, cv) => pv * cv, init);
        return new Math(value);
    }

    divide(...args) {
        const init = this.hasInitValue ? this.value : args.shift();
        const value = args.reduce((pv, cv) => pv / cv, init);
        return new Math(value);
    }

    toJSON() {
        return this.valueOf();
    }

    toString() {
        return String(this.valueOf());
    }

    valueOf() {
        return this.value;
    }

    [Symbol.toPrimitive](hint) {
        const value = this.value;
        if (hint === 'string') {
            return String(value);
        } else {
            return value;
        }
    }
}

export default new Math();

设计⼀个⽅法以判断是否回⽂(颠倒后的字符串和原来的字符串⼀样为回⽂)

function isPalindrome(str) {
    if (typeof str !== 'string') {
        return false
    }
    return str.split('').reverse().join('') === str
}

// 测试
console.log(isPalindrome('HelleH')); // true
console.log(isPalindrome('Hello')); // false

设计⼀个⽅法以统计字符串中出现最多次数的字符

function findMaxDuplicateChar(str) {
    let cnt = {};   //用来记录所有的字符的出现频次
    let c = '';        //用来记录最大频次的字符
    for (let i = 0; i < str.length; i++) {
        let ci = str[i];
        if (!cnt[ci]) {
            cnt[ci] = 1;
        } else {
            cnt[ci]++;
        }
        if (c == '' || cnt[ci] > cnt[c]) {
            c = ci;
        }
    }
    console.log(cnt); // { H: 1, e: 1, l: 3, o: 2, ' ': 1, W: 1, r: 1, d: 1 }
    return c;
}

// 测试
console.log(findMaxDuplicateChar('Hello World')); // l

合并二维有序数组成一维有序数组,归并排序的思路

function merge(left, right) {
  let result = []
  while (left.length > 0 && right.length > 0) {
    if (left[0] < right[0]) {
      result.push(left.shift())
    } else {
      result.push(right.shift())
    }
  }
  return result.concat(left).concat(right)
}
function mergeSort(arr) {
  if (arr.length === 1) {
    return arr
  }
  while (arr.lengt
         h > 1) {
    let arrayItem1 = arr.shift();
    let arrayItem2 = arr.shift();
    let mergeArr = merge(arrayItem1, arrayItem2);
    arr.push(mergeArr);
  }
  return arr[0]
}

let arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3], [4, 5, 6]];
let arr2 = [[1, 4, 6], [7, 8, 10], [2, 6, 9], [3, 7, 13], [1, 5, 12]];
console.log(mergeSort(arr1))
console.log(mergeSort(arr2))

给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。(难)

首先,我们肯定需要封装一个函数,而这个函数接收一个字符串作为参数,返回不含有重复字符的子串长度。来看下面的示例:

示例 1:

输入: “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。

示例 2:

输入: “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。

示例 3:

输入: “pwwkew” 输出: 3 解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。 请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

示例代码:

var lengthOfLongestSubstring = function (s) {
    var y = [];
    var temp = [];
    var maxs = 0;
    if (s == "") {
        return 0;
    }
    if (s.length == 1) {
        return 1;
    }
    for (var i = 0; i < s.length; i++) {
        if (temp.includes(s[i])) {

            y.push(temp.length);
            temp.shift();
            continue;
        } else {
            temp.push(s[i])
            y.push(temp.length);
        }

    }
    for (var j = 0; j < y.length; j++) {
        if (maxs <= y[j]) {
            maxs = y[j]
        }
    }
    return maxs;
};
// 测试
console.log(lengthOfLongestSubstring('abcabcbb')); // 3
console.log(lengthOfLongestSubstring('bbbbb')); // 1
console.log(lengthOfLongestSubstring('pwwkew')); // 3

有一堆整数,请把他们分成三份,确保每一份和尽量相等(11,42,23,4,5,6 4 5 6 11 23 42 56 78 90)(难)

本道题目是一道考察算法的题目,主要是考察编程基本功和一定的想像力。

const testArr = [11, 42, 23, 4, 5, 6, 4, 5, 6, 11, 23, 42, 56, 78, 90];
function avarageSum(n, arr) {
 //    找到平均值
 const sum = arr.reduce((a, b) => a + b, 0)
 const ava = Math.round(sum / n);
 // 生成一个长度为n的二维数组
 let target = new Array(n).fill(0).map(()=>[])
 // const target = [[], [], []];
 let cursor;
 let max = 0;
 let maxIdx = 0;
 // 所有数组需要尽量加到ava
 while (arr.length > 0) {
     // 得到当前arr数组最大的数
     // 出数器
     // 出数器负责拿出当前数组中最大的那个数
     for (let i = 0; i < arr.length; i++) {
         if (arr[i] > max) {
             max = arr[i];
             maxIdx = i
         }
     }
     // arr数组中删除最大数
     const temparr = arr.splice(maxIdx, 1);

     // 计算差距的函数 找到差距(距离平均值)最大的那一组 返回下标
     cursor = getMaxDis(target, ava);

     // console.log("下标", cursor)
     // 加入
     target[cursor].push(temparr[0]);
     // 重置
     max = 0;
     maxIdx = 0;
 }
 return target.map(item=>{
     // 返回累加结果
     let sum = item.reduce((a,b)=>a+b,0);
     item.sum = sum
     return item
 })

}
function getMaxDis(origin, stand) {
 // 计算origin数组中和stand最大差距的那一组
 const len = origin.length;
 let i = 0;
 let maxDis;
 let maxDisIdx;
 while (i < len) {
     const sum = origin[i].reduce((a, b) => a + b, 0);
     const dis = stand - sum;
     if (i === 0) {
         maxDis = dis;
         maxDisIdx = 0
     }
     if (dis > maxDis) {
         maxDis = dis;
         maxDisIdx = i
     }
     i++;
 }
 return maxDisIdx

}
console.log(avarageSum(3, testArr))
/*
  [
      [ 90, 23, 11, 6, 5, sum: 135 ],
      [ 78, 42, 11, 4, sum: 135 ],
      [ 56, 42, 23, 6, 5, 4, sum: 136 ]
   ]
 */

手写发布订阅(难)

<body>
  <div id="app">
    <p>this is a test</p>
    {{msg}}<input type="text" v-model="msg" />{{msg}}
  </div>
  <script src="./index.js"></script>
  <script>
    const vm = new Vue({
      el: "#app",
      data: {
        msg: "",
      },
    });
  </script>
</body>
/*
    1. 创建 Vue 构造函数
        在 Vue 构造函数中,调用了 observer 函数,该函数的作用就是对数据进行劫持
        劫持具体要做的事儿:复制一份数据,但是不是单纯的复制,而是增加了 getter、setter
    2. 书写 compile 函数。该函数主要作用于模板,从模板里面要提取信息
        提取的东西主要有两个:{{}}  和 v-model
    3. 创建发布者 Dep 的构造函数,如果数据发生变化,发布者就会遍历内部的数组(花名册),通知订阅者修改数据
    4. 创建订阅者 Watcher 的构造函数,如果有数据的变化,发布者就会通知订阅者,订阅者上面存在 update 方法,会进行修改
 */

function Vue(options) {
  // this 代表 Vue 的实例对象,本例中就是 vm
  // options.data 这就是实际的数据 {msg : 'xiejie'}
  observer(this, options.data);
  this.$el = options.el;
  compile(this);
}

// 用于对模板进行信息提取,主要提取 {{}}  和 v-model,然后进行一些操作
// {{ }} 会成为观察者,v-model 所对应的控件来绑定事件
function compile(vm) {
  var el = document.querySelector(vm.$el); // el 所对应的值为 <div id="app">...</div>
  var documentFragment = document.createDocumentFragment(); // 创建了一个空的文档碎片
  var reg = /\{\{(.*)\}\}/; // 创建正则表达式 匹配 {{ }}
  while (el.childNodes[0]) {
    var child = el.childNodes[0]; // 将第一个子节点存储到 child
    if (child.nodeType == 1) {
      // 如果能够进入此 if,说明该节点是一个元素节点
      for (var key in child.attributes) {
        // 遍历该元素节点的每一个属性,拿到的就是 type="text" v-model="msg"
        var attrName = child.attributes[key].nodeName; // 获取属性名  type、v-model
        if (attrName === "v-model") {
          var vmKey = child.attributes[key].nodeValue; // 先获取属性值,也就是 msg
          // 为该节点,也就是 <input type="text" v-model="msg"> 绑定一个 input 事件
          child.addEventListener("input", function (event) {
            vm[vmKey] = event.target.value; // 获取用户输入的值,然后改变 vm 里面的 msg 属性对应的值,注意这里会触发 setter
          });
        }
      }
    }
    if (child.nodeType == 3) {
      // 如果能进入此 if,说明该节点是一个文本节点
      if (reg.test(child.nodeValue)) {
        // 如果能够进入到此 if,说明是 {{ }},然后我们要让其成为订阅者
        var vmKey = RegExp.$1; // 获取正则里面的捕获值,也就是 msg
        // 实例化一个 Watcher(订阅者),接收 3 个参数:Vue 实例,该文本节点,捕获值 msg
        new Watcher(vm, child, vmKey);
      }
    }
    documentFragment.appendChild(el.childNodes[0]); // 将第一个子节点添加到文档碎片里面
  }
  // 将文档碎片中节点重新添加到 el,也就是 <div id="app"></div> 下面
  el.appendChild(documentFragment);
}

// 新建发布者构造函数
function Dep() {
  // 将观察者添加到发布者内部的数组里面
  // 这样以便于通知所有的观察者去更新数据
  this.subs = [];
}

Dep.prototype = {
  // 将 watcher 添加到发布者内置的数组里面
  addSub: function (sub) {
    this.subs.push(sub);
  },
  // 遍历数组里面所有的 watcher,通知它们去更新数据
  notify: function () {
    this.subs.forEach(function (sub) {
      sub.update();
    });
  },
};

// 新建观察者 Watcher 构造函数
// 接收 3 个参数:Vue 实例,文本节点 {{ msg }} 以及捕获内容 msg
function Watcher(vm, child, vmKey) {
  this.vm = vm; // vm
  this.child = child; // {{ msg }}
  this.vmKey = vmKey; // msg
  Dep.target = this; // 将该观察者实例对象添加给 Dep.target
  this.update(); // 执行节点更新方法
  Dep.target = null; // 最后清空 Dep.target
}
Watcher.prototype = {
  // 节点更新方法
  update: function () {
    // 相当于:{{ msg }}.nodeValue = this.vm['msg']
    // 这样就更新了文本节点的值,由于这里在获取 vm.msg,所以会触发 getter
    this.child.nodeValue = this.vm[this.vmKey];
  },
};

// 该函数的作用是用于数据侦听
function observer(vm, obj) {
  var dep = new Dep(); // 新增一个发布者:发布者的作用是告诉订阅者数据已经更改
  // 遍历数据
  for (var key in obj) {
    // 将数据的每一项添加到 vm 里面,至此,vm 也有了每一项数据
    // 但是不是单纯的添加,而是设置了 getter 和 setter
    // 在获取数据时触发 getter,在设置数据时触发 setter
    Object.defineProperty(vm, key, {
      get() {
        console.log("触发get了");
        // 触发 getter 时,将该 watcher 添加到发布者维护的数组里面
        if (Dep.target) {
          dep.addSub(Dep.target); // 往发布者的数组里面添加订阅者
        }
        console.log(dep.subs);
        return obj[key];
      },
      set(newVal) {
        console.log("触发set了");
        obj[key] = newVal;
        dep.notify(); // 发布者发出消息,通知订阅者修改数据
      },
    });
  }
}

手写用 ES6proxy 如何实现 arr[-1] 的访问

const proxyArray = (arr) => {
  const length = arr.length;
  return new Proxy(arr, {
    get(target, key) {
      key = +key;
      while (key < 0) {
        key += length;
      }
      return target[key];
    },
  });
};
var a = proxyArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
console.log(a[1]); // 2
console.log(a[-10]); // 9
console.log(a[-20]); // 8

提取高度嵌套的对象里的指定属性?

一般会使用递归的方式来进行查找。下面是一段示例代码:

function findKey(data, field) {
    let finding = '';
    for (const key in data) {
        if (key === field) {
            finding = data[key];
        }
        if (typeof (data[key]) === 'object') {
            finding = findKey(data[key], field);
        }
        if (finding) {
            return finding;
        }
    }
    return null;
}
// 测试
console.log(findKey({
    name: 'zhangsan',
    age: 18,
    stuInfo: {
        stuNo: 1,
        classNo: 2,
        score: {
            htmlScore: 100,
            cssScore: 90,
            jsScore: 95
        }
    }
}, 'cssScore')); // 90