不积跬步无以致千里。javascript
Step-By-Step (点击进入项目) 是我于 2019-05-20
开始的一个项目,项目愿景:一步一个脚印,量变引发质变。css
Step-By-Step 仅会在工做日发布面试题,主要考虑到部分小伙伴平时工做较为繁忙,或周末有出游计划。每一个周末我会仔细阅读你们的答案,整理最一份较优答案出来,因本人水平有限,有误的地方,你们及时指正。参与答题的小伙伴,能够对比本身的回答。html
答题不是目的,不但愿你们仅仅是简单的搜索答案,复制粘贴到issue下。更多的是但愿你们及时查漏补缺 / 巩固相关知识。html5
更多优质文章可戳: https://github.com/YvetteLau/...java
若是用一句话说明 this 的指向,那么便是: 谁调用它,this 就指向谁。node
可是仅经过这句话,咱们不少时候并不能准确判断 this 的指向。所以咱们须要借助一些规则去帮助本身:git
this 的指向能够按照如下顺序判断:github
浏览器环境:不管是否在严格模式下,在全局执行环境中(在任何函数体外部)this 都指向全局对象 window
; 面试
node 环境:不管是否在严格模式下,在全局执行环境中(在任何函数体外部),this 都是空对象 {}
;canvas
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
的实现原理:
[[原型]]
链接。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; }
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);
xxx.fn()
function info(){ console.log(this.age); } var person = { age: 20, info } var age = 28; person.info(); //20;执行的是隐式绑定
非严格模式: node环境,执行全局对象 global,浏览器环境,执行全局对象 window。
严格模式:执行 undefined
function info(){ console.log(this.age); } var age = 28; //严格模式;抛错 //非严格模式,node下输出 undefined(由于全局的age不会挂在 global 上) //非严格模式。浏览器环境下输出 28(由于全局的age不会挂在 window 上) //严格模式抛出,由于 this 此时是 undefined info();
箭头函数没有本身的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
点击查看更多
ES10新增了一种基本数据类型:BigInt
复杂数据类型只有一种: Object
null 不是一个对象,尽管 typeof null
输出的是 object
,这是一个历史遗留问题,JS 的最第一版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头表明是对象,null
表示为全零,因此将它错误的判断为 object
。
let b = { age: 10 } let a = b; a.age = 20; console.log(a); //{ age: 20 }
函数传参都是按值传递(栈中的存储的内容):基本数据类型,拷贝的是值;复杂数据类型,拷贝的是引用地址
//基本数据类型 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 }
点击查看更多
语义化意味着顾名思义,HTML5的语义化指的是合理正确的使用语义化的标签来建立页面结构,如 header,footer,nav,从标签上便可以直观的知道这个标签的做用,而不是滥用div。
<aside>
的内容可用做文章的侧栏。例如使用这些可视化标签,构建下面的页面结构:
对于早期不支持 HTML5 的浏览器,如IE8及更早以前的版本,咱们能够引入 html5shiv 来支持。
点击查看更多
==
操做符在左右数据类型不一致时,会先进行隐式转换。
a == 1 && a == 2 && a == 3
的值意味着其不多是基本数据类型。由于若是 a 是 null 或者是 undefined bool类型,都不可能返回true。
所以能够推测 a 是复杂数据类型,JS 中复杂数据类型只有 object
,回忆一下,Object 转换为原始类型会调用什么方法?
[Symbol.toPrimitive]
接口,那么调用此接口,若返回的不是基本数据类型,抛出错误。若是没有部署 [Symbol.toPrimitive]
接口,那么根据要转换的类型,先调用 valueOf
/ toString
hint
是 default
时,调用顺序为:valueOf
>>> toString
,即valueOf
返回的不是基本数据类型,才会继续调用 valueOf
,若是toString
返回的还不是基本数据类型,那么抛出错误。hint
是 string
(Date对象默人的hint是string) ,调用顺序为:toString
>>> valueOf
,即toString
返回的不是基本数据类型,才会继续调用 valueOf
,若是valueOf
返回的还不是基本数据类型,那么抛出错误。hint
是 number
,调用顺序为: 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
接口的状况,再也不作说明。
使用 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
toString
接口默认调用数组的 join
方法,重写数组的 join
方法。let a = [1, 2, 3]; a.join = a.shift; console.log(a == 1 && a == 2 && a == 3); //true
with
关键字我本人对 with
向来是敬而远之的。不过 with
的确也是此题方法之一:
let i = 0; with ({ get a() { return ++i; } }) { console.log(a == 1 && a == 2 && a == 3); //true }
点击查看更多
防抖函数的做用就是控制函数在必定时间内的执行次数。防抖意味着N秒内函数只会被执行一次,若是N秒内再次被触发,则从新计算延迟时间。
举例说明:小思最近在减肥,可是她很是贪吃。为此,与其男友约定好,若是10天不吃零食,就能够购买一个包(不要问为何是包,由于包治百病)。可是若是中间吃了一次零食,那么就要从新计算时间,直到小思坚持10天没有吃零食,才能购买一个包。因此,管不住嘴的小思,没有机会买包(悲伤的故事)...这就是防抖。
无论吃没吃零食,每10天买一个包,中间想买包,忍着,等到第十天的时候再买,这种状况是节流。如何控制女友的消费,各位攻城狮们,get到了吗?防抖可比节流有效多了!
timer
是 null
,调用 later()
,若 immediate
为true
,那么当即调用 func.apply(this, params)
;若是 immediate
为 false
,那么过 wait
以后,调用 func.apply(this, params)
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吗?
[5] https://digcss.com/throttle-t...
谢谢各位小伙伴愿意花费宝贵的时间阅读本文,若是本文给了您一点帮助或者是启发,请不要吝啬你的赞和Star,您的确定是我前进的最大动力。https://github.com/YvetteLau/...
推荐关注本人公众号: