绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
【Step-By-Step】一周面试题 && 答案汇总 / 周刊01
2020-05-21 12:00:57

关于【Step-By-Step】

不积跬步无以至千里。

Step-By-Step 是我于 2019-05-20 开始的一个项目,项目愿景:一步一个脚印,量变引起质变。

Step-By-Step 仅会在工作日发布面试题,主要考虑到部分小伙伴平时工作较为繁忙,或周末有出游计划。每个周末我会仔细阅读大家的答案,整理一份较优答案出来,因本人水平有限,有误的地方,大家及时指正。参与答题的小伙伴,可以对比自己的回答。

答题不是目的,不希望大家仅仅是简单的搜索答案,复制粘贴到issue下。更多的是希望大家及时查漏补缺 / 巩固相关知识。

已经过去的一周中,大家回答的都非常认真,希望小伙伴们能够一如既往的坚持。同样也欢迎更多的小伙伴一起参与进来,如果想 加群 学习,可以通过公众号,添加我为好友,验证信息为加入组织

Step-By-Step项目地址:https://github.com/YvetteLau/Step-By-Step

1.如何正确判断this的指向?(2019-05-20)

如果用一句话说明 this 的指向,那么即是: 谁调用它,this 就指向谁。

但是仅通过这句话,我们很多时候并不能准确判断 this 的指向。因此我们需要借助一些规则去帮助自己:

this 的指向可以按照以下顺序判断:

1. 全局环境中的 this

浏览器环境:无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this 都指向全局对象 window;

node 环境:无论是否在严格模式下,在全局执行环境中(在任何函数体外部),this 都是空对象 {};

2. 是否是 new 绑定

如果是 new 绑定,并且构造函数中没有返回 function 或者是 object,那么 this 指向这个新对象。如下:

构造函数返回值不是 function 或 object。

  1. function Super(age) {

  2. this.age = age;

  3. }


  4. let instance = new Super('26');

  5. console.log(instance.age); //26

构造函数返回值是 function 或 object,这种情况下 this 指向的是返回的对象。

  1. function Super(age) {

  2. this.age = age;

  3. let obj = {a: '2'};

  4. return obj;

  5. }



  6. let instance = new Super('hello');

  7. console.log(instance.age); //undefined

你可以想知道为什么会这样?我们来看一下 new 的实现原理:

  1. 创建一个新对象。

  2. 这个新对象会被执行 [[原型]] 连接。

  3. 属性和方法被加入到 this 引用的对象中。并执行了构造函数中的方法.

  4. 如果函数没有返回其他对象,那么 this 指向这个新对象,否则 this 指向构造函数中返回的对象。

  1. function new(func) {

  2. let target = {};

  3. target.__proto__ = func.prototype;

  4. let res = func.call(target);

  5. //排除 null 的情况

  6. if (res && typeof(res) == "object" || typeof(res) == "function") {

  7. return res;

  8. }

  9. return target;

  10. }

3. 函数是否通过 call,apply 调用,或者使用了 bind 绑定,如果是,那么this绑定的就是指定的对象【归结为显式绑定】。

  1. function info(){

  2. console.log(this.age);

  3. }

  4. var person = {

  5. age: 20,

  6. info

  7. }

  8. var age = 28;

  9. var info = person.info;

  10. info.call(person); //20

  11. info.apply(person); //20

  12. info.bind(person)(); //20

这里同样需要注意一种特殊情况,如果 call,apply 或者 bind 传入的个参数值是 undefined 或者 null,严格模式下 this 的值为传入的值 null /undefined。非严格模式下,实际应用的默认绑定规则,this 指向全局对象(node环境为global,浏览器环境为window)

  1. function info(){

  2. //node环境中:非严格模式 global,严格模式为null

  3. //浏览器环境中:非严格模式 window,严格模式为null

  4. console.log(this);

  5. console.log(this.age);

  6. }

  7. var person = {

  8. age: 20,

  9. info

  10. }

  11. var age = 28;

  12. var info = person.info;

  13. //严格模式抛出错误;

  14. //非严格模式,node下输出undefined(因为全局的age不会挂在 global 上)

  15. //非严格模式。浏览器环境下输出 28(因为全局的age会挂在 window 上)

  16. info.call(null);

4. 隐式绑定,函数的调用是在某个对象上触发的,即调用位置上存在上下文对象。典型的隐式调用为: xxx.fn()

  1. function info(){

  2. console.log(this.age);

  3. }

  4. var person = {

  5. age: 20,

  6. info

  7. }

  8. var age = 28;

  9. person.info(); //20;执行的是隐式绑定

5. 默认绑定,在不能应用其它绑定规则时使用的默认规则,通常是独立函数调用。

非严格模式: node环境,执行全局对象 global,浏览器环境,执行全局对象 window。

严格模式:执行 undefined

  1. function info(){

  2. console.log(this.age);

  3. }

  4. var age = 28;

  5. //严格模式;抛错

  6. //非严格模式,node下输出 undefined(因为全局的age不会挂在 global 上)

  7. //非严格模式。浏览器环境下输出 28(因为全局的age不会挂在 window 上)

  8. //严格模式抛出,因为 this 此时是 undefined

  9. info();

6. 箭头函数的情况:

箭头函数没有自己的this,继承外层上下文绑定的this。

  1. let obj = {

  2. age: 20,

  3. info: function() {

  4. return () => {

  5. console.log(this.age); //this继承的是外层上下文绑定的this

  6. }

  7. }

  8. }


  9. let person = {age: 28};

  10. let info = obj.info();

  11. info(); //20


  12. let info2 = obj.info.call(person);

  13. info2(); //28

点击查看更多

2.JS中原始类型有哪几种?null 是对象吗?原始数据类型和复杂数据类型有什么区别?(2019-05-21)

目前,JS原始类型有六种,分别为:

  • Boolean

  • String

  • Number

  • Undefined

  • Null

  • Symbol(ES6新增)

ES10新增了一种基本数据类型:BigInt

复杂数据类型只有一种: Object

null 不是一个对象,尽管 typeofnull 输出的是 object,这是一个历史遗留问题,JS 的初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象, null 表示为全零,所以将它错误的判断为 object 。

基本数据类型和复杂数据类型的区别为:

  1. 内存的分配不同

基本数据类型存储在栈中。

复杂数据类型存储在堆中,栈中存储的变量,是指向堆中的引用地址。

  1. 访问机制不同

  • 基本数据类型是按值访问

  • 复杂数据类型按引用访问,JS不允许直接访问保存在堆内存中的对象,在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值。

  1. 复制变量时不同(a=b)

  • 基本数据类型:a=b;是将b中保存的原始值的副本赋值给新变量a,a和b完全独立,互不影响

  • 复杂数据类型:a=b;将b保存的对象内存的引用地址赋值给了新变量a;a和b指向了同一个堆内存地址,其中一个值发生了改变,另一个也会改变。

  1. let b = {

  2. age: 10

  3. }


  4. let a = b;

  5. a.age = 20;

  6. console.log(b); //{ age: 20 }

  1. 参数传递的不同(实参/形参)

函数传参都是按值传递(栈中的存储的内容):基本数据类型,拷贝的是值;复杂数据类型,拷贝的是引用地址

  1. //基本数据类型

  2. let b = 10


  3. function change(info) {

  4. info=20;

  5. }

  6. //info=b;基本数据类型,拷贝的是值得副本,二者互不干扰

  7. change(b);

  8. console.log(b);//10

  1. //复杂数据类型

  2. let b = {

  3. age: 10

  4. }


  5. function change(info) {

  6. info.age = 20;

  7. }

  8. //info=b;根据第三条差异,可以看出,拷贝的是地址的引用,修改互相影响。

  9. change(b);

  10. console.log(b);//{ age: 20 }

点击查看更多

3.说一说你对HTML5语义化的理解(2019-05-22)

语义化意味着顾名思义,HTML5的语义化指的是合理正确的使用语义化的标签来创建页面结构,如 header,footer,nav,从标签上即可以直观的知道这个标签的作用,而不是滥用div。

语义化的优点有:

  • 代码结构清晰,易于阅读,利于开发和维护

  • 方便其他设备解析(如屏幕阅读器)根据语义渲染网页。

  • 有利于搜索引擎优化(SEO),搜索引擎爬虫会根据不同的标签来赋予不同的权重

语义化标签主要有:

  • title:主要用于页面的头部的信息介绍

  • header:定义文档的页眉

  • nav:主要用于页面导航

  • main:规定文档的主要内容。对于文档来说应当是的。它不应包含在文档中重复出现的内容,比如侧栏、导航栏、版权信息、站点标志或搜索表单。

  • article:独立的自包含内容

  • h1~h6:定义标题

  • ul: 用来定义无序列表

  • ol: 用来定义有序列表

  • address:定义文档或文章的作者/拥有者的联系信息。

  • canvas:用于绘制图像

  • dialog:定义一个对话框、确认框或窗口

  • aside:定义其所处内容之外的内容。 <aside> 的内容可用作文章的侧栏。

  • section:定义文档中的节(section、区段)。比如章节、页眉、页脚或文档中的其他部分。

  • figure:规定独立的流内容(图像、图表、照片、代码等等)。figure 元素的内容应该与主内容相关,但如果被删除,则不应对文档流产生影响。

  • details:描述文档或者文档某一部分细节

  • mark:义带有记号的文本。

语义化应用

例如使用这些可视化标签,构建下面的页面结构:

对于早期不支持 HTML5 的浏览器,如IE8及更早之前的版本,我们可以引入 html5shiv 来支持。

点击查看更多

4.如何让 (a == 1 && a == 2 && a == 3) 的值为true?

4.1 利用隐式转换规则

== 操作符在左右数据类型不一致时,会先进行隐式转换。

a==1&&a==2&&a==3 的值意味着其不可能是基本数据类型。因为如果 a 是 null 或者是 undefined bool类型,都不可能返回true。

因此可以推测 a 是复杂数据类型,JS 中复杂数据类型只有 object,回忆一下,Object 转换为原始类型会调用什么方法?

如果部署了 [Symbol.toPrimitive] 接口,那么调用此接口,若返回的不是基本数据类型,抛出错误。

如果没有部署 [Symbol.toPrimitive] 接口,那么根据要转换的类型,先调用 valueOf / toString

  1. 1. Date类型对象,`hint` `default` 时,调用顺序为:`valueOf` >>> `toString`,即`valueOf` 返回的不是基本数据类型,才会继续调用 `toString`,如果`toString` 返回的还不是基本数据类型,那么抛出错误。

  2. 2. 如果 `hint` `string`(Date对象的hint默认是string) ,调用顺序为:`toString` >>> `valueOf`,即`toString` 返回的不是基本数据类型,才会继续调用 `valueOf`,如果`valueOf` 返回的还不是基本数据类型,那么抛出错误。

  3. 3. 如果 `hint` `number`,调用顺序为: `valueOf` >>> `toString`

  1. var obj = {

  2. [Symbol.toPrimitive](hint) {

  3. console.log(hint);

  4. return 10;

  5. },

  6. valueOf() {

  7. console.log('valueOf');

  8. return 20;

  9. },

  10. toString() {

  11. console.log('toString');

  12. return 'hello';

  13. }

  14. }

  15. console.log(obj + 'yvette'); //default

  16. //如果没有部署 [Symbol.toPrimitive]接口,调用顺序为`valueOf` >>> `toString`

  17. console.log(obj == 'yvette'); //default

  18. //如果没有部署 [Symbol.toPrimitive]接口,调用顺序为`valueOf` >>> `toString`

  19. console.log(obj * 10);//number

  20. //如果没有部署 [Symbol.toPrimitive]接口,调用顺序为`valueOf` >>> `toString`

  21. console.log(Number(obj));//number

  22. //如果没有部署 [Symbol.toPrimitive]接口,调用顺序为`valueOf` >>> `toString`

  23. console.log(String(obj));//string

  24. //如果没有部署 [Symbol.toPrimitive]接口,调用顺序为`toString` >>> `valueOf`

那么对于这道题,只要 [Symbol.toPrimitive] 接口,次返回的值是 1,然后递增,即成功成立。

  1. let a = {

  2. [Symbol.toPrimitive]: (function(hint) {

  3. let i = 1;

  4. //闭包的特性之一:i 不会被回收

  5. return function() {

  6. return i++;

  7. }

  8. })()

  9. }

  10. console.log(a == 1 && a == 2 && a == 3); //true

调用 valueOf 接口的情况:

  1. let a = {

  2. valueOf: (function() {

  3. let i = 1;

  4. //闭包的特性之一:i 不会被回收

  5. return function() {

  6. return i++;

  7. }

  8. })()

  9. }

  10. console.log(a == 1 && a == 2 && a == 3); //true

另外,除了i自增的方法外,还可以利用 正则,如下

  1. let a = {

  2. reg: /\d/g,

  3. valueOf () {

  4. return this.reg.exec(123)[]

  5. }

  6. }

  7. console.log(a == 1 && a == 2 && a == 3); //true

调用 toString 接口的情况,不再做说明。

4.2 利用数据劫持

使用 Object.defineProperty 定义的属性,在获取属性时,会调用 get 方法。利用这个特性,我们在 window 对象上定义 a 属性,如下:

  1. let i = 1;

  2. Object.defineProperty(window, 'a', {

  3. get: function() {

  4. return i++;

  5. }

  6. });

  7. console.log(a == 1 && a == 2 && a == 3); //true

ES6 新增了 Proxy ,此处我们同样可以利用 Proxy 去实现,如下:

  1. let a = new Proxy({}, {

  2. i: 1,

  3. get: function () {

  4. return () => this.i++;

  5. }

  6. });

  7. console.log(a == 1 && a == 2 && a == 3); // true

4.3 数组的 toString 接口默认调用数组的 join 方法,重写数组的 join 方法。

  1. let a = [1, 2, 3];

  2. a.join = a.shift;

  3. console.log(a == 1 && a == 2 && a == 3); //true

4.4 利用 with 关键字

我本人对 with 向来是敬而远之的。不过 with 的确也是此题方法之一:

  1. let i = ;


  2. with ({

  3. get a() {

  4. return ++i;

  5. }

  6. }) {

  7. console.log(a == 1 && a == 2 && a == 3); //true

  8. }

点击查看更多

5.防抖(debounce)函数的作用是什么?有哪些应用场景,请实现一个防抖函数。

防抖函数的作用

防抖函数的作用就是控制函数在一定时间内的执行次数。防抖意味着N秒内函数只会被执行一次,如果N秒内再次被触发,则重新计算延迟时间。

举例说明:小思近在减肥,但是她非常贪吃。为此,与其男朋友约定好,如果10天不吃零食,就可以购买一个包(不要问为什么是包,因为包治百病)。但是如果中间吃了一次零食,那么就要重新计算时间,直到小思坚持10天没有吃零食,才能购买一个包。所以,管不住嘴的小思,没有机会买包(悲伤的故事)...这就是防抖

不管吃没吃零食,每10天买一个包,中间想买包,忍着,等到第十天的时候再买,这种情况是节流。如何控制女朋友的消费,各位攻城狮们,get到了吗?防抖可比节流有效多了!

防抖应用场景

  1. 搜索框输入查询,如果用户一直在输入中,没有必要不停地调用去请求服务端接口,等用户停止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力。

  2. 表单验证

  3. 按钮提交事件。

  4. 浏览器窗口缩放,resize事件等。

防抖函数实现

immediate 为 true 时,表示开始会立即触发一次。

immediate 为 false 时,表示后一次一定会触发。

loadsh 中的 debounce 的第三个参数 option ,提供了 leading 和 trailing两个参数。

  1. function debounce(func, wait, immediate=true) {

  2. let timeout, context, args;

  3. // 延迟执行函数

  4. const later = () => setTimeout(() => {

  5. // 延迟函数执行完毕,清空定时器

  6. timeout = null

  7. // 延迟执行的情况下,函数会在延迟函数中执行

  8. // 使用到之前缓存的参数和上下文

  9. if (!immediate) {

  10. func.apply(context, args);

  11. context = args = null;

  12. }

  13. }, wait);

  14. let debounced = function (...params) {

  15. if (!timeout) {

  16. timeout = later();

  17. if (immediate) {

  18. //立即执行

  19. func.apply(this, params);

  20. } else {

  21. //闭包

  22. context = this;

  23. args = params;

  24. }

  25. } else {

  26. clearTimeout(timeout);

  27. timeout = later();

  28. }

  29. }

  30. debounced.cancel = function () {

  31. clearTimeout(timeout);

  32. timeout = null;

  33. };

  34. return debounced;

  35. };

点击查看更多

参考文章:

[1] https://www.ecma-international.org/ecma-262/6.0/#sec-completion-record-specification-type

[2] 嗨,你真的懂this吗?

[3] 【面试篇】寒冬求职季之你必须要懂的原生JS(上)

[4] 【面试篇】寒冬求职季之你必须要懂的原生JS(中)

谢谢各位小伙伴愿意花费宝贵的时间阅读本文,如果本文给了您一点帮助或者是启发,请不要吝啬你的赞和Star,您的肯定是我前进的大动力。https://github.com/YvetteLau/Blog

关注公众号,加入交流群。


分享好友

分享这个小栈给你的朋友们,一起进步吧。

前端进阶之路
创建时间:2020-05-21 10:18:21
一位会拍照的程序媛来分享前端进阶之路
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • 刘小夕
    栈主

小栈成员

查看更多
  • 小雨滴
  • ?
  • 栈栈
  • 叶子,你好
戳我,来吐槽~