一周汇总javascript
1.如何正确判断this的指向?css
2.JS中原始类型有哪几种?null 是对象吗?原始数据类型和复杂数据类型有什么区别?html
3.说一说你对HTML5语义化的理解html5
4.如何让 (a == 1 && a == 2 && a == 3) 的值为true?java
5.防抖(debounce)函数的做用是什么?有哪些应用场景,请实现一个防抖函数。node
更多优质文章可戳: github.com/YvetteLau/B…git
若是用一句话说明 this 的指向,那么便是: 谁调用它,this 就指向谁。github
可是仅经过这句话,咱们不少时候并不能准确判断 this 的指向。所以咱们须要借助一些规则去帮助本身:面试
this 的指向能够按照如下顺序判断:canvas
浏览器环境:不管是否在严格模式下,在全局执行环境中(在任何函数体外部)this 都指向全局对象 window
;
node 环境:不管是否在严格模式下,在全局执行环境中(在任何函数体外部),this 都是空对象 {}
;
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(b); //{ 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
返回的不是基本数据类型,才会继续调用 toString
,若是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] www.ecma-international.org/ecma-262/6.…
[2] 嗨,你真的懂this吗?
谢谢各位小伙伴愿意花费宝贵的时间阅读本文,若是本文给了您一点帮助或者是启发,请不要吝啬你的赞和Star,您的确定是我前进的最大动力。github.com/YvetteLau/B…