# JS 笔试真题-1

# 快速导航

# 前言

以下一些题,均来各大小公司现场的真实笔试题,所有面试题都经过笔者一一验证,如有不足之处,谢谢小可爱们指正和完善

通过做题,可以了解自己知识存在的盲点,掌握知识才最重要

# 第 1 题. 变量声明不会提升

直接写出下边程序的输出结果

console.log(foo);
var foo = 'test';
1
2
点击即可查看

答案: undefined

解析: 变量foo,相当于只有var foo;但是却没有赋值,所以值是undefined,并且变量的声明并不会提升,只有函数声明会提升

# 第 2 题 词法作用域

写出下面一段代码输出结果

var temp = 123;
function f() {
  console.log(temp);
  if (false) {
    var temp = 456;
  }
}
f();
1
2
3
4
5
6
7
8
点击即可查看

答案: undefined

解析: 在没有调用 f 函数时,temp的值是123,在查找变量时,它先会从函数内沿着作用域链逐级向外进行查找,若有则会返回,若无,则会返回undefined,

使用var声明的变量并不会有块级作用域,在if语句块声明赋值的变量,相当于是裸露的,因为在 f 函数内,变量先使用,但是未赋值,所以是undeifined

# 第 3 题 块级作用域

写出下面一段代码输出的结果

var temp = 123;
function f() {
  console.log(temp);
  if (true) {
    console.log(temp);
    let temp = 456;
  }
}
f();
1
2
3
4
5
6
7
8
9
点击即可查看详情

答案: 123,然后报错ReferenceError: Cannot access 'temp' before initialization,无法在初始化之前访问变量

解析: let声明的变量是有块级作用域的,不能重复声明定义

# 第 4 题-Promise 执行问题

写出下面一段程序的输出的结果

new Promise((resolve) => {
  console.log(1);
  setTimeout(() => console.log(2), 0);
  Promise.resolve().then(() => console.log(3));
  resolve();
}).then(() => console.log(4));
console.log(5);
1
2
3
4
5
6
7
点击可查看详情

答案: 1 5 3 4 2

解析:事件循环,任务队列的执行顺序,new Promise是会立即调用,接收了一个resolve参数,执行调用输出console.log(1)语句,遇到setTimeout,它是一个异步宏任务,会推送到任务队列中,进入挂起状态

promise.resove也是一个异步微任务,仍然会推送到任务队列中,进入挂起状态,当主线程console.log(5)执行完后,在从异步任务队列中进行执行,在同一栈中,微任务要优先于宏任务的执行,所以先执行 3,然后 4,最后是 2

最终执行的顺序结果是:1,5,3,4,2

# 第 5 题-"123456789".match(/\d{3,5}?/g)的结果是什么?

正则表达式: 考察match的用法

点击即可查看详情

答案: ["123", "456", "789"]

解析:

match方法的返回值是存放首次匹配内容的数组。如果没有找到匹配结果,返回 null

用法stringObject.match(searchvalue | regexp),这里介绍一下regexp模式

返回值:存放匹配结果的数组。该数组的内容依赖于 regexp 是否具有全局标志 g

使用描述:

match() 方法将检索字符串stringObject,以找到一个或多个与regexp匹配的文本。这个方法的行为在很大程度上有赖于 regexp是否具有标志g

如果 regexp 没有标志 g,即全局匹配,那么match()方法就只能在stringObject 中执行一次匹配

如果没有找到任何匹配的文本,match() 将返回 null。否则,它将返回一个数组,其中存放了与它找到的匹配文本有关的信息

如下示例所示

let str = 'itclanCoder';
console.log(str.match('C'));
1
2

输出结果

['C', (index: 6), (input: 'itclanCoder'), (groups: undefined)];
1

该数组的第 0 个元素存放的是匹配文本,而其余的元素存放的是与正则表达式的子表达式匹配的文本。除了这些常规的数组元素之外,返回的数组还含有两个对象属性。 index属性声明的是匹配文本的起始字符在 stringObject中的位置,input 属性声明的是对 stringObject 的引用

如果 regexp 具有标志 g,则 match()方法将执行全局检索,找到 stringObject 中的所有匹配子字符串。

若没有找到任何匹配的子串,则返回 null。

如果找到了一个或多个匹配子串,则返回一个数组。不过全局匹配返回的数组的内容与前者大不相同,它的数组元素中存放的是 stringObject中所有的匹配子串,而且也没有 index 属性或 input属性

示例:

  • 不带修饰符 g
var url = 'http://www.baidu.com?a=1&b=2&c=3';
var reg = /([^?&=]+)=([^?&=])*/;
var result = url.match(reg);
console.log(result); //["a=1", "a", "1", index: 21, input: "http://www.baidu.com?a=1&b=2&c=3", groups: undefined]
console.log(result.index); // 21
console.log(result.input); // http://www.baidu.com?a=1&b=2&c=3
1
2
3
4
5
6
  • 带修饰符的 g
var url = 'http://www.baidu.com?a=1&b=2&c=3';
var reg = /([^?&=]+)=([^?&=])*/g;
var result = url.match(reg);
console.log(result); //["a=1", "b=2", "c=3"]
console.log(result.index); //undefined
console.log(result.input); //undefined
1
2
3
4
5
6

有全局 g 匹配和无g,是不一样的

而在本题中的"123456789".match(/\d{3,5}?/g),从123456789字符传中第 3 个位置查找,结束位置在第 5 个位置,全局匹配查找,最终会返回一个数组[123, 456, 789]

这个match方法有时候会很有用,在匹配某查找一字符串参数的时候,经常在面试中,被问到,怎么将一url中的参数给提取出来的,此时这个方法就很有用了的

# 第 6 题-编程将字符串"2018-11-03"转换成"11/03/2018"

字符串中一些方法操作:split

点击即可查看详情
var str = '2018-11-03';
var arrs = str.split('-'); // 经果 split 的分割,返回结果是一个数组,arrs 的结果为["2018", "11", "03"]
var result = arrs.map((item, index, arr) => {
  return arr[1] + '/' + arr[2] + '/' + arr[0]; // 当然也是可以通过 join 方法进行拼接的["11", "03","2018"].join('/')
});
var tranResult = Array.from(new Set(result)); // 数组去重.这里也可以使用拓展运算符 [...new Set(result)]
console.log(tranResult[0]); // 11/03/2018
1
2
3
4
5
6
7

注意

其中这个Array.from是可以将类数组格式,转化为真实的数组,经过new Set()后,set数据结构,如果不进行转化,是没有办法通过数组访问下标的方式进行访问的

这个Array.from方法不仅可以将 Set 生成数组,也可以从 String中生成数组,从 Map生成数组都是可以的,从类数组对象(arguments)生成数组,将一个类(伪)数组转换为一个真实的数组

具体详细使用可参考 MDN 文档Array.from 使用 (opens new window)

# 第 7 题-如果浏览器不支持 bind 函数,实现一个函数让其兼容

主要考察bind方法

  • 作用: 创建一个新的函数(称为绑定函数), 当它被调用时,将其 this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列
  • 与 call,apply 方法区别: 都是Function对象内置的方法,挂载在Function原型下共享方法,它们的第一个参数都是用来更改调用方法中 this 的指向。需要注意的是 bind 是返回新的函数,以便稍后调用,它往往跟事件结合使用,只有触发了事件,才会调用,而apply 、call 则是立即调用原函数
点击即可查看









 


const module = {
  name: 'itclanCoder',
  getName: function() {
    return this.name;
  },
};

const unboundGetName = module.getName;
console.log(unboundGetName()); //  函数在全局范围内被调用,undefined,这里的this指向的是window,很多面试题在全局范围定义了一个var name = "川川",那么此处便会输出"川川",因为这里全局作用域内没有name,所以是undefined
const boundGetName = unboundGetName.bind(module); // 通过bind改变this的指向,指向module对象
console.log(boundGetName()); // itclanCoder
1
2
3
4
5
6
7
8
9
10
11

在上面的示例代码中,将一个方法从对象中拿出来(上面的 getName 就是 module 对象下的一个方法),然后再调用(module.getName 赋值给 unboundGetName),总期望方法中的 this 是原来的对象(在这里指的是 module 对象,比如在回调中传入这个方法)。

如果不做特殊处理的话,一般会丢失原来的对象。基于这个函数,用原始的对象创建一个绑定函数,巧妙地解决了这个问题(上面使用 bind 后就解决了这个问题,让 this 指向了 module 对象)

  • 语法
// 所要改变的this指向的那个函数.bind(thisArg[, arg1[, arg2[, ...]]])
function.bind(thisArg[, arg1[, arg2[, ...]]])
1
2
  • thisArg: 当绑定函数被调用时,该参数会作为 this 的指向。当使用 new 操作符调用绑定函数时,该参数无效,会被忽略,当使用 bindsetTimeout中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为object。如果 bind 函数的参数列表为空,或者 thisArg 是 null 或 undefined,执行作用域的 this 将被视为新函数的 thisArg

  • arg1, arg2: 当目标函数被调用时,被预置入绑定函数的参数列表中的参数

  • 返回值:

返回一个原函数的拷贝,并拥有指定的 this 值和初始参数

  • 应用场景 1:可以对一个函数预设初始参数

只要将这些参数(如果有的话)作为 bind() 的参数写在 this 后面。当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们后面,如下所示

点击即可查看
function list() {
  return Array.prototype.slice.call(arguments); // 将类数组转换成真正的数组
}

function addArguments(arg1, arg2) {
  return arg1 + arg2;
}

var list1 = list(1, 2, 3); // [1, 2, 3]
var result1 = addArguments(1, 2); // 3

// 创建一个函数,它拥有预设参数列表。
var leadingThirtysevenList = list.bind(null, 37);

// 创建一个函数,它拥有预设的第一个参数
var addThirtySeven = addArguments.bind(null, 37);

var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
var result2 = addThirtySeven(5); // 37 + 5 = 42
var result3 = addThirtySeven(5, 10); // 37 + 5 = 42 ,第二个参数会被忽略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • 应用场景 2: 与setTimeout一起使用

在默认情况下,使用 window.setTimeout()时,此时this关键字会指向 window (或 global)对象。当类的方法中需要 this指向类的实例时,你可能需要显式地把this绑定到回调函数,这样就不会丢失该实例的引用

也就是说,当使用类的方法时需要this指向类实例,就可以使用bind()将 this 绑定到回调函数来管理实例

点击即可查看详情
function Bloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// 在 1 秒钟后声明 bloom
Bloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000); // 这里的this原本指向window,但是通过bind方法后,指向Bloomer
};

Bloomer.prototype.declare = function() {
  console.log('我有 ' + this.petalCount + ' 朵花瓣!');
};

var flower = new Bloomer();
flower.bloom(); // 1秒后输出 我有 11 朵花瓣
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

注意

对于事件处理函数和 setInterval 方法也可以使用上面的方法

  • 应用场景 3:绑定函数作为构造函数

绑定函数也适用于使用new操作符来构造目标函数的实例。当使用绑定函数来构造实例

注意

this 会被忽略,但是传入的参数仍然可用

如下实例代码所示

点击即可查看
function Point(x, y) {
  // 声明构造器Point函数
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function() {
  // 构造器Point方法原型下添加toString
  return this.x + ',' + this.y;
};

var p = new Point(1, 2);
p.toString(); // '1,2'

var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0 /*x*/);
// 实现中的例子不支持,
// 原生bind方法支持
var YAxisPoint = Point.bind(null, 0 /*x*/);

var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // '0,5'

axisPoint instanceof Point; // true,axisPoint是Point的实例化出来的一对象
axisPoint instanceof YAxisPoint; // true,axisPoint是YAxisPoint实例化出来的一对象
new Point(17, 42) instanceof YAxisPoint; // true // 同上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

在上面的示例代码中,PointYAxisPoint是共享原型对象,因此使用 instanceof 运算符判断时为 true

  • 应用场景 4:快捷调用-将类数组对象转换为真实的数组

bind()也可以为需要特定 this 值的函数创造捷径

var slice = Array.prototype.slice;

slice.call(arguments);
1
2
3

如果使用 bind()的话,情况变得更简单

// // 与前一段代码的 "slice" 效果相同,下面使用了bind
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(unboundSlice);

slice(arguments);
1
2
3
4
5

在上面的示例代码中,bind()有很多的使用场景,但是 bind()函数是在 ECMA-262 第五版才被加入;它可能无法在所有浏览器上运行。这就需要我们自己实现 bind()函数了,在面试题中,可以说是屡见不爽,屡见屡面

  • 方法 1: 模拟实现 bind 方法
点击即可查看实现模拟 bind 方法
//在Function的原型上自定义myBind()方法
Function.prototype.myBind = function myBind(context) {
  //获取要操作的函数
  var _this = this;
  //获取实参(context除外)
  var args = Array.prototype.slice.call(arguments, 1);
  //判断当前浏览器是否兼容bind()方法
  if ('bind' in Function.prototype) {
    //如果浏览器兼容bind()方法,则使用bind()方法,并返回bind()方法执行后的结果
    return _this.bind(context, args);
  }
  //如果不兼容bind()方法,则返回一个匿名函数
  return function() {
    _this.apply(context, args);
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 自建 bind 函数来给一个对象绑定事件
  • 思路: 三个参数分别是 对象事件回调函数
  • 兼容性: 通过 if 判断对象是否存在addEventListener方法来区分浏览器,当然也可以跟上面一样通过bind是不是Function下的原型对象
  • this 问题的解决:由于传入的回调函数是浏览器调用的,我们是无法去直接操作的,所以我们在attachEvent()不直接传入回调函数,而是先定义一个匿名函数,然后在函数内部调用回调函数,并利用 call 方法改变 this
点击即可查看
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title></title>
    <script type="text/javascript">
      window.onload = function() {
        var btn1 = document.getElementById('btn1');
        bind(btn1, 'click', function() {
          alert(this);
        });
      };
      //定义一个函数bind(),用来为指定元素绑定事件响应函数
      /*
       * 参数:
       *  obj 要绑定事件的对象
       *  eventStr 事件的字符串
       *  func 回调函数
       */
      function bind(obj, eventStr, func) {
        //判断是否有addEventListener()方法
        if (obj.addEventListener) {
          //大部分浏览器兼容的方式,addEventListener是标准浏览器支持的
          obj.addEventListener(eventStr, func, false);
        } else {
          //IE8及以下,注意使用的是on,绑定事件使用的是attachEvent
          //obj.attachEvent("on"+eventStr, func);//此方法this为window下面提供解决方法
          //统一this 不直接调用func而是在匿名函数内调用
          obj.attachEvent('on' + eventStr, function() {
            //在匿名函数内调用回调函数 利用call()方法将this改为obj
            func.call(obj);
          });
        }
      }
    </script>
  </head>
  <body>
    <button id="btn1">btn1</button>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

MDN 中 bind 使用相关介绍 (opens new window)

# 第 8 题-简单实现一下对象的深拷贝

考察点:基本数据类型的拷贝是赋值,而对象复杂数据类型的拷贝是赋止

解决: 递归实现深拷贝

点击即可查看
/*
  *  *
  *  @description:把一个对象递归拷贝给另外一个对象
  *  源对象与拷贝后的对象没有引用关系,实现克隆
  *  @params {obj2} obj2为被拷贝的对象
  *  @return {obj} obj为最终拷贝出来的对象
  *  @author itclanCoder
  *  */
function deepCopy(obj2){    // 递归函数
    var obj =  isArray(obj2)?[]:{}; // 判断被拷贝对象是数组还是对象,如果不考虑数组的话,会出现一些问题,如果被拷贝对象里面有数组对象的话,会把数组变为对象,如果传进来是一个数组的话,就让它是一个数组的,是一个对象就是一个对象的
    // 取出obj2的所有属性,如果当前拷贝的数据还是一个对象的话,那么继续调用,deepCopy进行二次拷贝,递归
    for(var property in obj2){
        if(isObject(obj2[property])){
            obj[property] = deepCopy(obj2[property])  // 递归
        }else{
            obj[property] =  obj2[property]
        }
    }
    return obj;
}

// 判断一个值是不是数组
function isArray(val){
    return Object.prototype.toString.call(val) === '[object Array]';
    // 这里直接用Es6中的Array.isArray()方法判断是不是数组也是可以的,但是上面那种是一些标准库的常见用法
}
// 判断是不是对象
function isObject(val){
    return typeof val ==='object' && val !== null;
}
var obj1 = {x: 1, y: 2, z: { a:3, b: 4}}
var obj2 = deepCopy(obj1);
obj2.x = 100;
obj2.z.a = 200;
console.log(obj1, obj2);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

具体与浅拷贝-深拷贝,可参考文章 JavaScript 中的浅拷贝 VS 深拷贝

# 第 9 题-实现一个数组的快速排序

原理:取出数组的中间项,然后把数组剩下的每一项与该项进行比较

如果小于该项就放在该项的左边,大于该项就放在该项的右边,再分别把左边的项和右边的项的中间项取出

重复上面的步骤到项数小于等于 1 为止,最后把所获得的项进行拼接

点击即可查看
var ary = [21, 8, 4, 32, 5];
function quickSort(ary) {
  if (ary.length <= 1) {
    return ary;
  }

  var num = Math.floor(ary.length / 2);
  var numValue = ary.splice(num, 1)[0];
  var left = [];
  var right = [];
  for (var i = 0; i < ary.length; i++) {
    var cur = ary[i];
    if (cur < numValue) {
      left.push(cur);
    } else {
      right.push(cur);
    }
  }
  return quickSort(left).concat([numValue], quickSort(right));
}
console.log(quickSort(ary));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 第 10 题-实现一个数组的插入排序

原理:取出数组的第一项放到新数组里,让剩余的每一项与新数组的每一项进行比较,小的放左边,大的放右边

点击即可查看
var ary = [21, 8, 4, 32, 5];
function insertSort(ary) {
  var left = ary.splice(0, 1);
  for (var i = 0; i < ary.length; i++) {
    var cur = ary[i];
    for (var j = left.length - 1; j >= 0; ) {
      var nex = left[j];
      if (cur < nex) {
        j--;
        if (j === -1) {
          left.unshift(cur);
        }
      } else {
        left.splice(j + 1, 0, cur);
        break;
      }
    }
  }
  return left;
}
console.log(insertSort(ary));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 第 11 题-实现一个数组的冒泡排序

原理:从第一项开始,与它后面的每一项进行比较,如果大于后面的项,则交换位置

点击即可查看
var ary = [43, 5, 82, 21];
function buddleSort(data) {
  var temp = '';
  for (var i = data.length; i > 0; i--) {
    for (var j = 0; j < i - 1; j++) {
      if (data[j] > data[j + 1]) {
        temp = data[j];
        data[j] = data[j + 1];
        data[j + 1] = temp;
      }
    }
  }
  return data;
}
console.log(buddleSort(ary));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 第 12 题 以下结果是 false 的选项有哪些

考察点:条件表达式中,有哪些结果值是 false 的

A. 0 == ''            // true
B. null == undefined  // true
C: NaN == NaN         // false
D: 0.1+0.2 === 0.3    // false
E: {} == []           // 报错
F: !{}                // false
G: !0                 // true
1
2
3
4
5
6
7
点击即可查看

答案: C D F

关于表达式的真假问题可以参考Js 之选择控制语句及很重要的表达式真与假

# 在线测试

白色

关注公众号

一个走心,有温度的号,同千万同行一起交流学习

加作者微信

扫二维码 备注 【加群】

扫码易购

福利推荐