【Step-By-Step】一周面试题 && 答案汇总 / 01

关于【Step-By-Step】

不积跬步无以致千里。javascript

Step-By-Step (点击进入项目) 是我于 2019-05-20 开始的一个项目,项目愿景:一步一个脚印,量变引发质变。css

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

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

更多优质文章可戳: https://github.com/YvetteLau/...java

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

若是用一句话说明 this 的指向,那么便是: 谁调用它,this 就指向谁。node

可是仅经过这句话,咱们不少时候并不能准确判断 this 的指向。所以咱们须要借助一些规则去帮助本身:git

this 的指向能够按照如下顺序判断:github

1. 全局环境中的 this

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

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

2. 是不是 new 绑定

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

构造函数返回值不是 function 或 object。
function Super(age) {
    this.age = age;
}

let instance = new Super('26');
console.log(instance.age); //26
构造函数返回值是 function 或 object,这种状况下 this 指向的是返回的对象。
function Super(age) {
    this.age = age;
    let obj = {a: '2'};
    return obj;
}


let instance = new Super('hello');
console.log(instance.age); //undefined

你能够想知道为何会这样?咱们来看一下 new 的实现原理:

  1. 建立一个新对象。
  2. 这个新对象会被执行 [[原型]] 链接。
  3. 属性和方法被加入到 this 引用的对象中。并执行了构造函数中的方法.
  4. 若是函数没有返回其余对象,那么 this 指向这个新对象,不然 this 指向构造函数中返回的对象。
function new(func) {
    let target = {};
    target.__proto__ = func.prototype;
    let res = func.call(target);
    //排除 null 的状况
    if (res && typeof(res) == "object" || typeof(res) == "function") {
        return res;
    }
    return target;
}

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

function info(){
    console.log(this.age);
}
var person = {
    age: 20,
    info
}
var age = 28;
var info = person.info;
info.call(person);   //20
info.apply(person);  //20
info.bind(person)(); //20

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

function info(){
    //node环境中:非严格模式 global,严格模式为null
    //浏览器环境中:非严格模式 window,严格模式为null
    console.log(this);
    console.log(this.age);
}
var person = {
    age: 20,
    info
}
var age = 28;
var info = person.info;
//严格模式抛出错误;
//非严格模式,node下输出undefined(由于全局的age不会挂在 global 上)
//非严格模式。浏览器环境下输出 28(由于全局的age会挂在 window 上)
info.call(null);

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

function info(){
    console.log(this.age);
}
var person = {
    age: 20,
    info
}
var age = 28;
person.info(); //20;执行的是隐式绑定

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

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

严格模式:执行 undefined

function info(){
    console.log(this.age);
}
var age = 28;
//严格模式;抛错
//非严格模式,node下输出 undefined(由于全局的age不会挂在 global 上)
//非严格模式。浏览器环境下输出 28(由于全局的age不会挂在 window 上)
//严格模式抛出,由于 this 此时是 undefined
info();

6. 箭头函数的状况:

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

let obj = {
    age: 20,
    info: function() {
        return () => {
            console.log(this.age); //this继承的是外层上下文绑定的this
        }
    }
}

let person = {age: 28};
let info = obj.info();
info(); //20

let info2 = obj.info.call(person);
info2(); //28
点击查看更多

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

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

  • Boolean
  • String
  • Number
  • Undefined
  • Null
  • Symbol(ES6新增)

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

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

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

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

  1. 内存的分配不一样
  • 基本数据类型存储在栈中。
  • 复杂数据类型存储在堆中,栈中存储的变量,是指向堆中的引用地址。
  1. 访问机制不一样
  • 基本数据类型是按值访问
  • 复杂数据类型按引用访问,JS不容许直接访问保存在堆内存中的对象,在访问一个对象时,首先获得的是这个对象在堆内存中的地址,而后再按照这个地址去得到这个对象中的值。
  1. 复制变量时不一样(a=b)
  • 基本数据类型:a=b;是将b中保存的原始值的副本赋值给新变量a,a和b彻底独立,互不影响
  • 复杂数据类型:a=b;将b保存的对象内存的引用地址赋值给了新变量a;a和b指向了同一个堆内存地址,其中一个值发生了改变,另外一个也会改变。
let b = {
    age: 10
}

let a = b;
a.age = 20;
console.log(a); //{ age: 20 }
  1. 参数传递的不一样(实参/形参)

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

//基本数据类型
let b = 10

function change(info) {
    info=20;
}
//info=b;基本数据类型,拷贝的是值得副本,两者互不干扰
change(b);
console.log(b);//10
//复杂数据类型
let b = {
    age: 10
}

function change(info) {
    info.age = 20;
}
//info=b;根据第三条差别,能够看出,拷贝的是地址的引用,修改互相影响。
change(b);
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. 非Date类型对象,hintdefault 时,调用顺序为:valueOf >>> toString,即valueOf 返回的不是基本数据类型,才会继续调用 valueOf,若是toString 返回的还不是基本数据类型,那么抛出错误。
    2. 若是 hintstring(Date对象默人的hint是string) ,调用顺序为:toString >>> valueOf,即toString 返回的不是基本数据类型,才会继续调用 valueOf,若是valueOf 返回的还不是基本数据类型,那么抛出错误。
    3. 若是 hintnumber,调用顺序为: valueOf >>> toString
var obj = {
    [Symbol.toPrimitive](hint) {
        console.log(hint);
        return 10;
    },
    valueOf() {
        console.log('valueOf');
        return 20;
    },
    toString() {
        console.log('toString');
        return 'hello';
    }
}
console.log(obj + 'yvette'); //default
//若是没有部署 [Symbol.toPrimitive]接口,调用顺序为`valueOf` >>> `toString`
console.log(obj == 'yvette'); //default
//若是没有部署 [Symbol.toPrimitive]接口,调用顺序为`valueOf` >>> `toString`
console.log(obj * 10);//number
//若是没有部署 [Symbol.toPrimitive]接口,调用顺序为`valueOf` >>> `toString`
console.log(Number(obj));//number
//若是没有部署 [Symbol.toPrimitive]接口,调用顺序为`valueOf` >>> `toString`
console.log(String(obj));//string
//若是没有部署 [Symbol.toPrimitive]接口,调用顺序为`toString` >>> `valueOf`

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

let a = {
    [Symbol.toPrimitive]: (function(hint) {
            let i = 1;
            //闭包的特性之一:i 不会被回收
            return function() {
                return i++;
            }
    })()
}
console.log(a == 1 && a == 2 && a == 3); //true

调用 valueOf 接口的状况:

let a = {
    valueOf: (function() {
        let i = 1;
        //闭包的特性之一:i 不会被回收
        return function() {
            return i++;
        }
    })()
}
console.log(a == 1 && a == 2 && a == 3); //true

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

let a = {
    reg: /\d/g,
    valueOf () {
        return this.reg.exec(123)[0]
    }
}
console.log(a == 1 && a == 2 && a == 3); //true

调用 toString 接口的状况,再也不作说明。

4.2 利用数据劫持

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

let i = 1;
Object.defineProperty(window, 'a', {
    get: function() {
        return i++;
    }
});
console.log(a == 1 && a == 2 && a == 3); //true

ES6 新增了 Proxy ,此处咱们一样能够利用 Proxy 去实现,以下:

let a = new Proxy({}, {
    i: 1,
    get: function () {
        return () => this.i++;
    }
});
console.log(a == 1 && a == 2 && a == 3); // true

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

let a = [1, 2, 3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3); //true

4.4 利用 with 关键字

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

let i = 0;

with ({
    get a() {
        return ++i;
    }
}) {
    console.log(a == 1 && a == 2 && a == 3); //true
}
点击查看更多

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

防抖函数的做用

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

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

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

防抖应用场景

  1. 搜索框输入查询,若是用户一直在输入中,没有必要不停地调用去请求服务端接口,等用户中止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力。
  2. 表单验证
  3. 按钮提交事件。
  4. 浏览器窗口缩放,resize事件等。

防抖函数实现

  1. 事件第一次触发时,timernull,调用 later(),若 immediatetrue,那么当即调用 func.apply(this, params);若是 immediatefalse,那么过 wait 以后,调用 func.apply(this, params)
  2. 事件第二次触发时,若是 timer 已经重置为 null(即 setTimeout 的倒计时结束),那么流程与第一次触发时同样,若 timer 不为 null(即 setTimeout 的倒计时未结束),那么清空定时器,从新开始计时。
function debounce(func, wait, immediate = true) {
    let timer;
    // 延迟执行函数
    const later = (context, args) => setTimeout(() => {
        timer = null;// 倒计时结束
        if (!immediate) {
            func.apply(context, args);
            //执行回调
            context = args = null;
        }
    }, wait);
    let debounced = function (...params) {
        let context = this;
        let args = params;
        if (!timer) {
            timer = later(context, args);
            if (immediate) {
                //当即执行
                func.apply(context, args);
            }
        } else {
            clearTimeout(timer);
            //函数在每一个等待时延的结束被调用
            timer = later(context, args);
        }
    }
    debounced.cancel = function () {
        clearTimeout(timer);
        timer = null;
    };
    return debounced;
};

immediate 为 true 时,表示函数在每一个等待时延的开始被调用。

immediate 为 false 时,表示函数在每一个等待时延的结束被调用。

只要高频事件触发,那么回调函数至少被调用一次。

点击查看更多

参考文章:

[1] https://www.ecma-internationa...

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

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

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

[5] https://digcss.com/throttle-t...

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

推荐关注本人公众号:

clipboard.png

相关文章
相关标签/搜索