JavaScript常见笔试题(持续更新)

4,871 阅读6分钟

最近在找实习,就记录一些自己平时看到的以及面试过程中碰到的代码题,如果有什么不对或者可以改进的地方希望大家批评指正~

一、HTML、CSS相关

1.水平居中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 块级元素水平居中,已知宽度 
    <style>
        /* 方法一:margin:0 auto */
        /* .father{
            width:500px;
            height:300px;
            background-color:blue;
        }
        .son{
            width:100px;
            height:100px;
            background-color:yellow;
            margin:0 auto; 
        } */

        /* 方法二:绝对定位 + left + margin-left */
        /* .father{
            width:500px;
            height:300px;
            background-color:blue;
            position:relative;
        }
        .son{
            width:100px;
            height:100px;
            background-color:yellow;
            position:absolute;
            left:50%;
            margin-left:-50px;
        } */
    </style> -->

    <!-- 块级元素水平居中,未知元素宽度 -->
    <style>
        /* 方法一:绝对定位 + left + translateX(-50%) */
        /* .father{
            width:500px;
            height:300px;
            background-color:blue;
            position:relative;
        }
        .son{
            height:100px;
            background-color:yellow;
            position:absolute;
            left:50%;
            transform:translateX(-50%);
            overflow:hidden;
            text-overflow:ellipsis;
        } */

        /* 方法二:flex */
        .father{
            width:500px;
            height:300px;
            background-color:blue;
            display:flex;
            justify-content:center;
        }
        .son{
            height:100px;
            background-color:yellow;
        }
    </style>
</head>
<body>
    <div class="father">
        <div class="son">我是块级元素我是块级元素</div>
    </div>
</body>
</html>

2.垂直居中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        /* position方法 */
        /* .father{
            width:300px;
            height:300px;
            background-color:red;
            position:relative;

        }
        .son{
            width:100px;
            height:100px;
            background-color:blue;
            position:absolute;
            top:50%;
            transform: translateY(-50%);
        } */

        /* flex方法 */
        .father{
            background-color:blue;
            width:300px;
            height:300px;
            display:flex;
            align-items:center;
        }
        .son{
            width:100px;
            height:100px;
            background-color:red;
        } 
        
    </style>
</head>
<body>
    <div class="father">
        <div class="son">垂直居中</div>
        
    </div>
</body>
</html>

3.获取页面所有checkbox

// 希望获取到页面中所有的checkbox怎么做?(不使用第三方框架)

var inputs = document.getElementsByTagName("input");
var checkboxArray = [];
for(let i = 0; i < inputs.length; i++){
    if(inputs[i].type === "checkbox"){
        checkboxArray.push(inputs[i]);
    }
}

4.表格行颜色变化

<!-- 请写一个表格以及对应的CSS,使表格奇数行为白色背景,偶数行为灰色背景,鼠标移上去时为黄色背景。 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>表格</title>
    <style type="text/css">
        body {
            margin: 0;
        }

        table {
            border: 1px solid #ddd;
            border-collapse: collapse; /* 去掉td之间的间隙 */

        }

        tr:nth-child(odd) {
            background-color: #fff
        }

        tr:nth-child(even) {
            background-color: #666
        }

        tr:hover {
            background-color: yellow
        }
    </style>
</head>

<body>
    <table>
        <th>表格标题</th>
        <tr>
            <td>1</td>
            <td>2</td>
            <td>3</td>
            <td>4</td>
            <td>5</td>
        </tr>
        <tr>
            <td>1</td>
            <td>2</td>
            <td>3</td>
            <td>4</td>
            <td>5</td>
        </tr>
        <tr>
            <td>1</td>
            <td>2</td>
            <td>3</td>
            <td>4</td>
            <td>5</td>
        </tr>
    </table>
</body>

</html>

二、JS、算法相关

1. Promise模拟实现

2. Promise.all实现

// promise.all 实现
// promise.all(iterable)返回一个新的promise实例。
// 此实例在iterable参数内所有的promise都fulfilled或者参数中不包含promise时,状态才变成fulfilled;
// 如果参数中有一个失败rejected,此实例回调失败,失败的信息是第一个失败promise的返回结果

Promise.all_ = function(promises) {
    return new Promise((resolve, reject) => {
        // Array.from()可将可迭代对象转化为数组
        promises = Array.from(promises);
        if(promises.length===0) {
            resolve([]);
        } else {
            let result = [];
            let index = 0;
            for(let i=0; i<promises.length; i++) {
                // 考虑到promise[i]可能是thenable对象也可能是普通值
                Promise.resolve(promises[i]).then(data => {
                    result[i] = data;
                    if(++index===promises.length) {
                        // 所有的promise状态都是fulfilled,promise.all返回的实例才变成fulfilled状态
                        resolve(result);
                    }
                }, err => {
                    reject(err);
                    return;
                })
            }
        }
    })
}

3.Promise.race实现

// promise.race
// Promise.race返回的仍然是一个Promise,它的状态与第一个完成的Promise的状态相同;
// 如果传入的参数是不可迭代的,那么将会抛出错误。

Promise.ra_ce = function(promises) {
    promises = Array.from(promises);
    return new Promise((resolve, reject) => {
        if(promises.length===0) {
            return;
        } else {
            for(let i=0; i<promises.length; i++) {
                Promise.resolve(promises[i]).then(data => {
                    resolve(data);
                    return;
                }, err => {
                    reject(err);
                    return;
                })
            }
        }
    })
}

4. call实现

// 模拟思路:
// 将函数设为对象的属性
// 执行该函数
// 删除该函数
Function.prototype.call2 = function (context) {
    // 将函数设为对象的属性,改变this指向(传null的时候,this指向window)
    context = context || window;
    context.fn = this;
    // 传参并执行函数,用一个数组来接收不定长参数
    var args = [];
    for(var i = 1; i < arguments.length; i ++){
        args.push('arguments['+i+']');
    }
    var result = eval('context.fn(' + args + ')');
    //删除该函数
    delete context.fn;
    return result;
}

5. apply实现

// 模拟apply()方法的实现

Function.prototype.apply2 = function(context,arr){
    context = context || window;
    context.fn = this;
    var result;
    if(!arr){
        result = context.fn()
    }else{
        var args = [];
        for(var i = 0; i < arr.length; i++){
            args.push('arr[' + i + ']')
        }
        result = eval('context.fn(' + args + ')')
    }
    delete context.fn;
    return result;
}

6. bind实现

// 模拟bind的实现
// bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。
// 1.返回一个函数
// 2.可以传入参数
//   传参时注意:函数可以只传一个参数,然后在执行返回的函数时再继续传参
// 3.bind函数可以用new来创建对象,相当于把原函数当作一个构造器,即当bind返回的函数作为构造函数的时候,bind指定的this失效了
// 4.调用bind的不是函数时要报错

Function.prototype.bind2 = function(context){
    if(typeof this !== "function"){
        throw new Error("")
    }
    var self = this;
    var args = Array.prototype.slice.call(arguments,1);
    var fNOP = function(){}
    var fBound =  function(){
        var bindArgs = Array.prototype.slice.call(arguments);
        self.apply(self instanceof fNOP ? this : context,args.concat(bindArgs));
    }
    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

7. 继承方法

// 构造函数继承
function Person1(name) {
    this.name = name;
}

function Student1(name, age) {
    Person1.call(this, name);
    this.age = age;
}
var s1 = new Student1("xuhanlin");
console.log(s1.name);


// 原型链继承
function Person2() {
    this.name = "renyuan";
}

function Student2(age) {
    this.age = age;
}
Student2.prototype = new Person2();
var s2 = new Student2();
console.log(s2.name);

// 组合继承
function Person3() {
    this.name = "renyuan";
}

function Student3() {
    Person3.call(this);
    this.age = "18";
}
Student3.prototype = new Person3();
var s3 = new Student3();
console.log(s3.name, s3.age);

8.闭包

// 闭包实现倒计时
for(var i = 10; i > 0; i--){
    (function(i){
        setTimeout(() => {
        console.log(i);
    },(10-i)*1000)
    })(i)
}

9.防抖

function debounce(func,wait){
    var timeout;
    return function(){
        var context = this;
        var args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(function(){
            func.apply(context,args);
        },wait);
    }
}
// https://github.com/mqyqingfeng/Blog/issues/22

10.节流

// 使用时间戳
// 当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 )
// 如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,
// 如果小于,就不执行。
function throttle(func,wait){
    return function(){
        var context = this;
        var args = arguments;
        var previous = 0;
        var now = +new Date();
        if(now - previous > wait){
            func.apply(context,args);
            previous = now;
        }
    }
}

// 设置定时器
function throttle(func,wait){
    var timeout;
    return function(){
        var context = this;
        var args = arguments;
        if(!timeout){
          timeout = setTimeout(function(){
              timeout = null;
            func.apply(context,args)
        },wait);  
        }
        
    }
}

11.new实现

//(1)首先创建了一个新的空对象
//(2)将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
//(3)让函数的 this 指向这个对象,执行构造函数的代码(也就是为这个对象添加属性和方法)
//(4)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
function create(Con, ...args) {
  let obj = {}
  Object.setPrototypeOf(obj, Con.prototype)
 //或者 obj.__proto__ = Con.prototype
  let result = Con.apply(obj, args)
  return result instanceof Object ? result : obj
}

12.数组去重

var array = [1,1,'1'];

// 方法一:indexOf()
// 创建一个新数组,遍历原数组,用indexOf判断原数组中的值是否已经存在,不存在就push进去
function unique(arr){
    var res = [];
    for(let item of arr){
        if(res.indexOf(item) === -1){
            res.push(item);
        }
    }
    return res;
}


// 方法二:排序后去重
function unique(arr){
    var res = [];
    var newArr = arr.sort();
    for(let i=0; i<newArr.length; i++){
        if(newArr[i] !== newArr[i+1]){
            res.push(newArr[i]);
        }
    }
    return res;
}

// 方法三:利用Set的唯一性
function unique(arr){
    return Array.from(new Set(arr));
}

// 方法四:Map
function unique (arr) {
    const map = new Map()
    return arr.filter((a) => !map.has(a) && map.set(a, 1))
}

console.log(unique(array));

13.数组扁平化

var arr = [1, [2, [3, 4]]];

// 方法一:循环数组,如果里面还是数组,递归调用扁平化方法
function flatten(arr) {
    var result = [];
    for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i])) {
            result = result.concat(flatten(arr[i]));
        } else {
            result.push(arr[i])
        }
    }
    return result;
}

// 方法一的改进写法
function flatten(arr) {
    return arr.reduce(function (pre, item) {
        return pre.concat(Array.isArray(item) ? flatten(item) : item)
    }, []);
}

// 方法二:toString(),它的一个缺点是改变了元素的类型,只适合于数组中元素都是整数的情况
function flatten(arr) {
    return arr.toString().split(",").map(function (item) {
        return +item;
    })
}

console.log(flatten(arr));

14.柯里化

function sub_curry(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        return fn.apply(this, args.concat([].slice.call(arguments)));
    };
}

function curry(fn, length) {

    length = length || fn.length;

    var slice = Array.prototype.slice;

    return function() {
        if (arguments.length < length) {
            var combined = [fn].concat(slice.call(arguments));
            return curry(sub_curry.apply(this, combined), length - arguments.length);
        } else {
            return fn.apply(this, arguments);
        }
    };
}

15.数组乱序

// 数组乱序
// 遍历数组元素,然后将当前元素与以后随机位置的元素进行交换

function shuffle(arr) {
    for (let i = arr.length; i > 0; i--) {
        let j = Math.floor(Math.random() * i)
        let x = arr[i - 1];
        arr[i - 1] = arr[j];
        arr[j] = x;
    }
    return arr;
}

var arr = [1, 2, 3, 4, 5];
console.log(shuffle(arr))

16.快速排序

// 快速排序
// 找到一个基准数,比基准数小的放左边,比基准数大的放右边
// 对左右区间不断重复这个过程

var quickSort = function(arr){
    if(arr.length <= 1){
        return arr;
    }
    const pivotIndex = Math.floor(arr.length / 2); //任意基准数索引
    const pivot = arr.splice(pivotIndex,1)[0]; //找到对应的基准数
    const left = [];
    const right = [];
    for(let i=0; i<arr.length; i++){
        if(arr[i] < pivot){
            left.push(arr[i]);
        }else{
            right.push(arr[i]);
        }
    }
    return quickSort(left).concat([pivot], quickSort(right));
}

const arr = [98, 42, 25, 54, 15, 3, 25, 72, 41, 10, 121];
console.log(quickSort(arr));

17.冒泡排序

// 冒泡排序
// 两两相邻元素对比,如果前一个比后一个大,就往后移,一轮比较下来,最大的数到了最后一个
// 重复这个步骤

function bubbleSort(arr) {
    for (let i = 0; i < arr.length; i++) {
        for (let j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                const temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    return arr;
}

var arr = [10, 14, 8, 5, 88];
console.log(bubbleSort(arr));

18.深浅拷贝

// 实现浅拷贝
// 遍历对象,然后把属性和属性值都放到一个新对象里
var shallowCopy = function(obj) {
    // 只拷贝对象
    if (typeof obj !== 'object') return;
    // 根据obj的类型判断是新建一个数组还是对象
    var newObj = obj instanceof Array ? [] : {};
    // 遍历obj,并且判断是obj的属性才拷贝
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}


// 实现深拷贝
// 在拷贝的时候判断一下属性值的类型,如果是对象,再递归拷贝一下
var deepCopy = function(obj){
    if(typeof(obj) !== "object") return;
    var newObj = obj instanceof Array ? [] : {};
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            newObj[key] = typeof obj[key] === "object" ? deepCopy(obj[key]) : obj[key];
        }
    }
    return newObj;
}

19.实现一个累加函数的功能比如 sum(1,2,3)(2).valueOf()

function sum(...args) {

let result = 0;

result = args.reduce(function (pre, item) {
  return pre + item;
}, 0);

let add = function (...args) {

  result = args.reduce(function (pre, item) {
    return pre + item;
  }, result);

  return add;
};

add.valueOf = function () {
  console.log(result);
}

return add;
}

20.大数相加

// js 实现一个函数,完成超过范围的两个大整数相加功能
function bigNumberAdd(a,b){
    var res = ""; //用字符串接收结果
    var carry;  //保存进位结果
    //把字符串转换成数组
    a = a.split("");
    b = b.split("");
    // 当数组的长度都变为0,并且最终不再进位时,结束循环
    while(a.length || b.length || carry){
        carry += a.pop() + b.pop();
        res += carry % 10;
        // 判断是否需要进位,true 和 false 的值在加法中会被转换为 1 和 0
        carry = carry > 9;
    }
    return res;
}

21.最大公约数、最小公倍数

// 求两个数的最大公约数
// 辗转相除法
// 用大的数对小的数取余,再用小的数对取余后的值取余,一直递归,直到余数为零
function getMaxCommonDivisor(a, b) {
    if (b === 0) {
        return a;
    }
    return getMaxCommonDivisor(b, a % b)
}

// 求最小公倍数
// 两数相乘,然后除以他们的最大公约数

function getMinCommonMutiple(a, b) {
    return a * b / getMaxCommonDivisor(a, b);
}

22.数组中重复的数字

// 方法三:
// 让对应索引的数到对应的位置,比如第一个数为2,就把它放到索引为2的地方
// 相当于把数放进对应的桶
var findRepeatNumber = function(nums){
    for(let i=0; i<nums.length; i++){
        while(i !== nums[i]){
            if(nums[i] == nums[nums[i]]){
                return nums[i];
            }else{
                var temp = nums[i];
                nums[i] = nums[temp];
                nums[temp] = temp;
            }

        }
    }
    // return -1;
}

23.旋转数组的最小数字

// 思路:
// (遍历数组,找出最小值 pass)
// 二分法:
// 因为是旋转数组,前面的元素都大于等于后面的元素,最小的元素是分界线
// 指针指向左(i)右(j)两端,跟中间的数(索引m)作比较,不断缩小比较区间
// nums[m] > nums[j], 中间的数大于最后的数,那最小数一定在后半段,令 i = m + 1;
// nums[m] < nums[j], 中间的数小于最后的数,那最小数一定在前半段,令 j = m;
// nums[m] === nums[j], 无法判断,用 j = j - 1 来缩小范围

var minArray = function (nums) {
    var i = 0;
    var j = nums.length - 1;
    while (i < j) {
        var m = Math.floor((i + j) / 2);
        if (numbers[m] > numbers[j]) {
            i = m + 1;
        } else if (numbers[m] < numbers[j]) {
            j = m;
        } else {
            j = j - 1;
        }
    }
    return numbers[i];
};

24.数组中超过一半的数

// 方法一:
//     使用map记录数组中的值以及出现的次数
var majorityElement = function(nums) {
    // write code here
const len = nums.length 
if (len === 0) return 0

const map = new Map()
for (let i of nums) {
    if (map.get(i) === undefined) {
        map.set(i, 1)
    }else {
        map.set(i, map.get(i)+1)
    }
}
for (let item of map.entries()) {
    if (item[1] > Math.floor(len/2)) return item[0]
}
return 0
};

// 方法二:给数组排序,次数超过一半的话,那么数组中点索引对应的数一定是这个数
var majorityElement = function(nums){
    var newNums = nums.sort();
    return newNums[Math.floor(nums.length / 2)];
};

25.构建乘积数组

// 思路:1.left:正向遍历A数组,累乘
//      2.right:反向遍历A数组,累乘
//      3.left * right 即为B数组

function constructArr(a) {
    var res = [];
    var left = 1;
    for (let i = 0; i < a.length; i++) {
        res[i] = left;
        left *= a[i];
    }
    var right = 1;
    for (let i = a.length - 1; i >= 0; i--) {
        res *= right;
        right *= a[i];
    }
    return res;
}

26.连续子数组最大和

var maxSubArray = function(nums){
    let max = -Infinity;
    nums.reduce((pre,cur) => {
        if(pre > 0){
            pre += cur; //连续子数组的和大于0,对求最大和起正向作用,就加上他
        }else{
            pre = cur;  //连续子数组的和小于0,加上他会越来越小,就不要她了,直接从当前值重新开始
        }
        max = max > pre ? max : pre;
        return pre;
    },0)
    return max;
}