前端javascript面试题经典集合(2020年最新)

1. call和apply的区别是什么,哪个性能更好一些

fn.call(obj, 10, 20, 30)
fn.apply(obj, [10, 20, 30])
call性能要比apply性能好一点,(尤其是传递给函数的参数超过三个的时候),所以后期研发的时候,可以使用call多一点

let arr = [10, 20, 30]
obj = {}
function fn(x, y, z) {}
fn.apply(obj, arr)
fn.call(obj, ...arr) // 基于ES6的展开运算符也可以实现把数组中的每一项一次传递给函数

实现性能测试:任何代码性能测试读书和测试环境有关系的,例如cpu、内存、GPU等电脑当前性能不会有相同的情况,不同浏览器也会导致性能上的不同

// console.time 可以测试出一段程序执行的时间
// console.profile()在火狐浏览器中安装firebug,可以更精准的获取到每个步骤所消耗的时间
console.time('A')
for(let i = 0; i< 100000; i++) {
    
}
console.timeEnd('A')

2. 实现(5).add(3).minus(2) ,使其输出结果为: 6

~function() {
  // 每一个方法执行完,都要返回number这个类型的实例,这样才可以用继续调取number类型原型中的方法
  function check(n) {
    n = Number(n);
    return isNaN(n)? 0 : n;
  }
    function add(n){
    n = check(n)
    return this + n
  }
  function minus(n) {
    n = check(n)
    return this -n
  }
  
  Number.prototype.add = add;
  Number.prototype.minus = minus

}()

3. 箭头函数与普通函数(function)的区别是什么?构造函数(function)可以使用new生成实力,那么箭头函数可以用吗?

箭头函数和普通函数的区别:

  1. 箭头函数语法比普通函数更加简洁(ES6中每一种函数都可以使用形参赋默认值和剩余运算符)
  2. 箭头函数没有自己的this,它里面出现的this是继承函数所处的上下文中的this(使用call/apply等任何方式都无法改变this的指向)
let fn = x => x + 1

// 普通函数
let obj = {
    name: 'lanfeng'
}
function fn1() {
    console.log(this)
}
fn1.call(obj) // {name: "lanfeng"}

// 箭头函数
let fn2 = () => {
    console.log(this)
}
fn2.call(obj) //Window {parent: Window, opener: null, top: Window, length: 3, frames: Window, …}

document.body.onclick = () => {
//=> this: window不是当前操作的body了
}

// 回调函数: 把伊尼戈函数B作为实参传递给另外一个函数A,函数A在执行的时候,可以把传递进来的函数去执行(执行N次,可传值)
function each(arr, callback) {
  for(let i = 0; i < arr.length; i++) {
    let item = arr[i],
        index = i;
    // 接收回调函数返回的结果,如果是false,则结束循环
    let flag = callback.call(arr,item, index)
    if(flag === false) {
        break;
    }
  }
}
each([10, 20, 30, 40], function(item, index){
    // this:原始操作数组
})
  1. 箭头函数中没有arguments(类数组),只能基于...arg获取传递参数集合(数组)
let fn = (...arg) => {
    console.log(arguments) //Uncaught ReferenceError: arguments is not defined
  console.log(arg) // [10, 20, 30n]
}
fn(10, 20, 30)

  1. 箭头函数不能被new执行(因为箭头函数没有this,也没有prototype)

4. 如何把一个字符串的大小写取反(大写变小写,小写变大写)

let str = "lanfengQIUqiu前端"
str = str.replace(/[a-zA-Z]/g, content => {
    // content:每一次正则匹配的结果
  // 验证是否为大写字母:把字母转换为大写和之前是否一样,之前是大写的:在ASII表中找到大写字母的取值范围进行判断
   return content.toUpperCase() === content ? content.toLowerCase() : content.toUpperCase()
})
console.log(str) // LANFENGqiuQIU前端

5. 实现一个字符串匹配算法,从字符串s中,查找是否存在字符串T,若存在返回所在位置,不存在返回-1(如果并不能基于indexOf/includes等内置的方法,你会如何处理呢)

循环原始字符串中的每一项,让每一项从当前位置向后截取T.length个字符,然后和T进行比较,
如果不一样,继续循环,如果一样返回当前索引即可(循环结束)

(function() {
  /**
    * 循环原始字符串中的每一项,让每一项从当前位置向后截取T.length个字符,然后和T进行比较,
       如果不一样,继续循环,如果一样返回当前索引即可(循环结束)
  **/
    function myIndexOf(T) {
    let lenT = T.length,
        lenS = this.length,
        res = -1
    if(lenT > lenS) return -1
    for(let i = 0; i < lenS- lenT + 1; i++) {
        let char = this[i];
      if(this.substr(i,lenT) === T) {
        res = i;
        break;
      }
    }
     return res
  }
  String.prototype.myIndexOf = myIndexOf
 
})();

 let S = 'zhufengpeixun',
      T = 'pei'
  console.log(S.myIndexOf(T))

正则处理

(function() {
  /**
    * 正则处理:exec方法
  **/
    function myIndexOf(T) {
    let reg = new RegExp(T),
        res = reg.exec(this);
    return res === null ? -1 : res.index;
  }
  String.prototype.myIndexOf = myIndexOf
 
})();

 let S = 'zhufengpeixun',
      T = 'pei'
  console.log(S.myIndexOf(T))

5. 在输入框中如何判断输入的是一个正确的网址,例如:用户输入一个字符串,验证是否符合URL网址格式

let str = "https://lanfeng.blog.csdn.net/"
let reg = /^((http|https|ftp):\/\/)?(([\w-]+\.)+[a-z0-9]+)((\/[^/]*)+)?(\?[^#]+)?(#.+)?$/i
// 1. 协议: // http/https/ftp
// 2. 域名
// 3. 请求路径
// 4. 问号传参
console.log(reg.exec(str))

6.

function Foo() {
    Foo.a = function() {
    console.log(1)
  }
  this.a = function() {
    console.log(2)
  }
}
// 把Foo当做类,在原型上设置实例公有的属性方法 => 实例.a()
Foo.prototype.a = function() {
    console.log(3)
}

// 把Foo当做普通对象设置私有属性方法 => Foo.a()
Foo.a = function() {
    console.log(4)
}
Foo.a(); //4
let obj = new Foo(); // obj可以调取原型上的方法 Foo.a
obj.a(); //2 //私有属性中有a
Foo.a(); //1

7. 编写代码实现图片懒加载

  • 前端性能优化的重要方案,通过图片或者数据的延迟加载,我们可以加快页面渲染的速度,让第一次打开页面的速度变快,只有滑动到某个区域,我们才加载真实的图片,这样也可以节省加载的流量
  • 处理方案:把所有需要延迟加载的图片用一个盒子包起来,设置宽高和默认占位图;开始让所有的img的src为空,把真实图片地址放到img的自定义属性上,让img隐藏;等到所有其他资源都加载完之后,我们再开始加载图片;对于很多图片,需要当页面滚动的时候,当前图片区域完全显示出来之后再加载真实图片

8. 编写一条正则,用来验证此规则:一个6~16位的字符串,必须同时包含有大小写字母和数字

let reg = /^(?!^[a-zA-Z]+$)(?!^[0-9]+$)(?!^[a-z0-9]+$)(?!^[A-Z0-9]+$)[a-zA-Z0-9]{6,16}/

9. 1-10位:数字、字母、下划线组成字符串,必须有_

let reg = /(?!^[a-zA-Z0-9]+$)^\w{1, 10}$/

10. 实现一个$attr(name, value)遍历,属性为name,值为value的元素集合,例如:

let ary = $attr('class', 'box') // 获取页面中所有class为box的元素

function $attr(property, value) {
    let elements = document.getElementsByTagName('*'),
      arr = [];
  [].forEach.call(elements, item => { 
    // 存储的是当前元素property对应的属性值
    let itemValue = item.getAttribute(property);
    if(property === 'class') {
        // 样式类属性名要特殊处理
      new RegExp('\\b' + value +'\\b').test(itemValue) ? arr.push(item) ? null
      return;
    }
    if(itemValue === value) {
        // 获取的值和传递的值校验成功:当前就是我们想要的
      arr.push(item)
    }
  })
  
  return arr 
}

10. 英文字母汉字组成的字符串,用正则给英文单词前后加空格

let str = 'no作no死,你能你can, 不能no哔哔',
    reg = /\b[a-z]+\b/ig;
str = str.replace(reg, value => {
    return ' ' + value + ' ';
}).trim();
console.log(str) // no 作 no 死,你能你 can , 不能 no 哔哔

11. 编写一个程序,将数组扁平化,并去除其中重复部分数据,最终得到一个升序且不重复的数组

let arr = [[1,2,3],[3,4,5,5],[6,7,8,9,[11, 12,[12,13,[14]]]],10]
arr = arr.flat(Infinity)

//使用ES6中推广的Array.prototype.flat处理
console.log(arr)
[...new Set(arr)]
arr = Array.from(new Set(arr).sort((a,b) => z-b))

//把数组变成字符串(数组tostring之后,不管数组多少级,最后都会变为以逗号分隔的字符串),直接扁平化了
arr = arr.toString().split(',').map(item => {
    return Number(item)
})

//JSON.stringify(arr)也可以扁平化数组
arr = JSON.stringify(arr).replace(/(\[|\])/g, '').split(',').map(item => Number(item))

//递归处理
~function () {
    function myFlat() {
    let result = [],
        _this = this;
    // 循环arr中的每一项,把不是数组的存储到新数组中
    let fn = (arr) => {
      for(let i = 0; i < arr.length; i++) {
        let item = arr[i];
        if(Array.isArray(item)) {
            fn(item);
          continue;
        }
        result.push(item)
      }
        
        };
    fn(_this);
    return result;
  }
  Array.prototype.myFlat = myFlat
}();
arr = arr.myFlat()

11. 有关构造函数

function Dog(name) {
    this.name = name;
}
Dog.prototype.bark = function() {
    console.log('wangwang');
}
Dog.prototype.sayName = function() {
    console.log('my name is' + this.name)
}
//基于内置的new关键词,可以创建Dog的一个实例sanmao,实例可以调取原型上的属性和方法,实现一个_new方法,也能模拟出内置new后的结果

// Fn当前要new的类
// arg后期需要给构造函数传递的参数信息
function _new(Fn, ...arg) {
  //let obj = {}
  //obj.__proto__ = Fn.prototype //创建一个空对象,让他的原型链指向Fn.prototype
  let obj = Object.create(Fn.prototype)
    Fn.call(obj, ...arg)
  return obj
}
/**
    * let sanmao = new Dog('三毛');
  * 1. 像普通函数执行一样,形成一个私有的作用域
  *    + 形参赋值
  *    + 变量提升
  * 2. 默认创建一个对象,让函数中的this执行这个对象,这个对象就是当前类的一个实例
  * 3. 代码执行
  * 4. 默认把创建的对象返回
**/
let sanmao = _new(Dog,'三毛');

sanmao.bark(); // wangwang
sanmao.sayName(); //"my name is 三毛"
console.log(sanmao instanceof Dog) // true

12. 有关数组合并和排序

let ary1 = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2'];
let ary2 = ['A', 'B', 'C', 'D']
// 合并后的数组为:['A1', 'A2','A', 'B1', 'B2','B', 'C1', 'C2','C', 'D1', 'D2', 'D'];
ary2 = ary2.map(item => item + '岚峰')
let arr = ary1.concat(ary2)
arr = arr.sort((a,b) => a.localeCompare(b)).map(item =>{
    return item.replace('岚峰', '')
})

console.log(arr)
let ary1 = ['D1', 'D2','A1', 'A2', 'B1', 'B2', 'C1', 'C2'];
let ary2 = ['B','A', 'C', 'D']
// 合并后的数组为:['D1', 'D2', 'D', 'A1', 'A2','A', 'B1', 'B2','B', 'C1', 'C2','C'];
let n = 0;
for(let i = 0; i< ary2.length; i++) {
    let item2 = ary2[i];
  for(let k = 0; k< ary1.length; k++) {
    let item1 = ary1[k];
    if(item1.includes(item2)) {
        n = k;
      continue;
    }
    break;
  }
  ary1.splice(n+1, 0, item2)
}

console.log(arr)

13. 定时器是异步编程:每一轮循环设置定时器,无需等定时器触发执行,继续下一轮循环(定时器触发的时候,循环已经结束)

for(var i = 0; i < 10; i++) {
  (function(i){
    setTimeout(() => {
        console.log(i)
    }, 1000)
  })(i)

}

//第二种
for(var i = 0; i < 10; i++) {
  setTimeout(((i) => {
        return () => {
        console.log(i)
      }
    })(i), 1000)

}

//第三种 可以基于bind预先处理机制:在循环的时候就把每次执行函数需要输出的结果,预先传给函数即可
for(var i = 0; i< 10; i++) {
    var fn = function(i) {
    console.log(i)
  };
  setTimeout(fn.bind(null, i), 1000)
}
//第四种,var变为let
for(let i = 0; i < 10; i++) {
  setTimeout(() => {
        console.log(i)
    }, 1000)

}

14. 有关变量

var b = 10;
(function() {
    b = 20;
  console.log(b) //20
})()
console.log(b) // 20

15.

/**
    * == 进行比较的时候,如果左右两边数据类型不一样,则先转换为相同的数据类型,然后再进行比较
  * 1. {} == {} 两个对象进行比较,比较的是堆内存的地址
  * 2. null == undefined 相等的, null === undefined 不相等
  * 3. NaN和谁都不相等 NaN == NaN 不相等
  * 4. [12] == '12' 对象和字符串相比较,是把对象toString转换为字符串后再进行比较的
  * 5. 剩余所有情况在进行比较的时候,都是转换为数字(前提数据类型不一样)
     对象转数字:先转换为字符串,再转化为数字
     字符串转数字:只要出现一个非数字字符,结果就是NaN
     undefined转数字NaN
  [12] == true =>false
  [] == false  => 0== 0 => true
  [] == 1  => 0 == 1 false
**/

// 1. 对象和数字比较:先把对象.toString()变为字符串,然后再转换为数字
var a = {
    n: 0,
  toString: function () {
    return  ++this.n
  }
};
// a.toString(); // 此时调取的就不再是Object.prototype.toString了,调取的是自己私有的
if(a == 1 && a ==2 & a==3) {
    console.log(1)
}
  
// 2. shift: 删除数组的第一项,把删除的内容返回,原有数组改变
var a = [1,2,3]
a.toString = a.shift
if(a == 1 && a == 2 && a==3) {
    console.log('Ok')
}

// 3. 
Object.defineProperty(window, 'a', {
    get: function() {   
    this.value ? this.value++ : this.value = 1
 
    return this.value
  }
});
if(a == 1 && a == 2 && a==3) {
    console.log('Ok')
}

16. 数组push方法原理运用

/**
    * Array.prototype.push = function(val) {
        this[this.length] = val
       //this.length在原来的基础上加1
       return this.length;
    }
**/

let obj = {
    2:3, //=>1
  3: 4, //=> 2
  length: 2, // => 3 => 4
  push: Array.prototype.push
}
obj.push(1) // this.obj => obj[obj.length] = 1 => obj[2] = 1 => obj.length = 3
obj.push(2) // this.obj => obj[obj.length] = 2 =>  obj[3] = 2 => obj.length = 4
console.log(obj) 

17. 数组当中三大经典排序算法: 冒泡排序

/**
    * 冒泡排序思想
  * 让数组中的当前项项和后一项进行比较,如果当前项比后一项大,则两项交换位置(让大的靠后)即可
**/
let ary = [12, 8, 24, 16, 1]

/**
    * bubble:实现冒泡排序
  * ary[Array] 需要排序的数组
  * @return 
  [Array] 排序h后的新数组
**/
function bubble(ary) {
  //外层循环i控制比较的轮数
  for(let i = 0; i<ary.length; i++) {
    // 里面循环控制每一轮比较的次数
   for(let j = 0; j< ary.length-1-i; j++) {
    if(ary[j]> ary[j+1]) {
        temp = ary[j];
      ary[j] = ary[j+1];
      ary[j+1] = temp
    }
   }
    
  }
  return ary
}
let ary = [12, 8, 24, 16, 1]
ary = bubble(ary)
console.log(ary)

18. 数组当中三大经典排序算法: 插入排序

/**
    * insert:实现插入排序
  * ary[Array] 需要排序的数组
  * @return 
  [Array] 排序h后的新数组
**/
function insert(ary) {
  // 1. 准备一个新数组,用来存储抓到手里的牌,
  let handle = [];
  handle.push(ary[0])
  for(let i = 1; i<ary.length; i++) {
    // A是新抓的牌
    let A = ary[1]
    // 和handle手里的牌依次比较(从后向前)
    for(let j = handle.length - 1; j>=0; j--) {
        let B =  handle[j]
      
      // 如果当前新牌A比要比较的B大,把A放到B的后面
      if(A>B) {
        handle.splice(j+1, 0 , A)
        breadk;
      }
      // 已经比到第一项,我们把新牌放到手中最前面即可
      if(j===0) {
        handle.unshift(A)
      }
    }
  }
    return handle
}
let ary = [12, 8, 24, 16, 1]
ary = insert(ary)
console.log(ary)

19. 数组当中三大经典排序算法: 快速排序

/**
    * quick:实现快速排序
  * ary[Array] 需要排序的数组
  * @return 
  [Array] 排序h后的新数组
**/
function quick(ary) {
  //结束递归(当ary中小于等于一项,则不用处理)
  if(ary.length <=1) {
    return aryy
  }
  // 1. 找到数组的中间项,在你原有的数组中把它拆除
  let middleIndex = Math.floor(arry.length/2);
  let middleValue = ary.splice(middleIndex, 1)[0]
  
  // 2. 准备左右两个数组,循环剩下数组中的每一项,比当前项晓的放到左边数组中,反之放到右边数组
  let aryLeft = [],
      aryRight = [];
  for(let i = 0; i< ary.length; i++) {
    let item = ary[i]
    item < middleValue ? aryLeft.push(item) : aryRight.push(item);
  }
  // 3. 递归方式让左右两边的数组持续这样处理,一直到左右两边都排好序为止
  
  return quick(aryyLeft)concat(middleValue, quick(aryRight));
}
let ary = [12, 8, 24, 16, 1]
ary = quick(ary)
console.log(ary)

20.

某公司1到12月份的销售额存在一个对象里面
如下: {
1: 222,
2: 123,
5: 888
}
请把数据处理为如下结构:[222, 123, null, null,888, null, null, null, null, null, null , null]

//第一种方法
let obj = {
    1: 222,
    2: 123,
    5: 888
}
let arr = new Array(12).fill(null).map((item, index) => {
    return obj[index + 1] || null
})
console.log(arr)

// 第二种方法
let obj = {
    1: 222,
    2: 123,
    5: 888
}
obj.length = 13
let arr = Array.from(obj).slice(1).map(item => {
 typeof item === 'undefined' ? null : item
})
console.log(arr)

// 第三种方法
let obj = {
    1: 222,
    2: 123,
    5: 888
}

let arr = new Array(12).fill(null)
Object.keys(obj).forEach(item => {
    arr[item-1] = obj[item];
})
console.log(arr)

21. 给定两个数组,写一个方法来计算它们的交集

// 第一种方法
let num1 = [1,2,2,1]
let num2 = [2,2]
//输出结果[2, 2]

let arr = [];
for(let i = 0; i< num1.length; i++) {
    let item1 = num1[i]
  for(let k = 0; k< num2.length; k++) {
    let item2 = num2[k]
    if(item1 === item2) {
     arr.push(item1)
      break;
    }
  }
}
console.log(arr)

// 第二种方法
let num1 = [1,2,2,1]
let num2 = [2,2]
let arr = [];
num1.forEach(item => {
    num2.includes(item) ? arr.push(item) : null
})
console.log(arr)

22.

实现一个add函数,满足以下功能
add(1); // 1
add(1)(2); // 3
add(1)(2)(3) // 6
add(1)(2)(3)(4); //10
add(1)(2, 3); // 6
add(1, 2)(3); //6
add(1,2,3); // 6
函数柯里化:预先处理的思想(利用闭包的机制)

let obj = {name: 'lanfeng'}
function fn(...arg) {
    console.log(this, arg)
}
// 点击的时候fn中的this => obj arg=> [100, 200, 事件对象]
document.body.onclick = fn.bind(obj, 100, 200);
// 执行bind方法,会返回一个匿名函数,当事件触发,匿名函数执行,我们仔处理fn即可
document.body.onclick = function(ev) {
    //ev事件对象:给每个元素的某个事件绑定方法,当事件触发会执行这个方法,并且把当前事件的相关信息传递给这个函数“事件对象”
}
function currying(fn, length) {
    length = length || fn.length;
  return function(...args) {
    if(args.length >= length) {
        return fn(...args);
    }
    return currying(fn.bind(null, ...args), length-args.length);
  }
}
function add(n1, n2, n3) {
    return n1 + n2 + n3;
}
add = currying(add, 3)
console.log(add(1)(2)(3))
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章