第一问:安全类型检测——typeof和instanceof 区别以及缺陷,以及解决方案
这两个方法都可以用来判断变量类型
区别:前者是判断这个变量是什么类型,后者是判断这个变量是不是某种类型,返回的是布尔值
(1)typeof
缺陷:
1.不能判断变量具体的数据类型比如数组、正则、日期、对象,因为都会返回object,不过可以判断function,如果检测对象是正则表达式的时候,在Safari和Chrome中使用typeof的时候会错误的返回"function",其他的浏览器返回的是object.
2.判断null的时候返回的是一个object,这是js的一个缺陷,判断NaN的时候返回是number
(2)instanceof
可以用来检测这个变量是否为某种类型,返回的是布尔值,并且可以判断这个变量是否为某个函数的实例,它检测的是对象的原型
let num = 1
num instanceof Number // false
num = new Number(1)
num instanceof Number // true
明明都是num,而且都是1,只是因为第一个不是对象,是基本类型,所以直接返回false,而第二个是封装成对象,所以true。
这里要严格注意这个问题,有些说法是检测目标的__proto__与构造函数的prototype相同即返回true,这是不严谨的,检测的一定要是对象才行,如:
let num = 1
num.__proto__ === Number.prototype // true
num instanceof Number // false
num = new Number(1)
num.__proto__ === Number.prototype // true
num instanceof Number // true
num.__proto__ === (new Number(1)).__proto__ // true
此外,instanceof还有另外一个缺点:如果一个页面上有多个框架,即有多个全局环境,那么我在a框架里定义一个Array,然后在b框架里去用instanceof去判断,那么该array的原型链上不可能找到b框架里的array,则会判断该array不是一个array。
解决方案:使用Object.prototype.toString.call(value) 方法去调用对象,得到对象的构造函数名。可以解决instanceof的跨框架问题,缺点是对用户自定义的类型,它只会返回[object Object]
第二问:既然提到了instanceof,那手写实现下instanceof吧
// [1,2,3] instanceof Array ---- true
// L instanceof R
// 变量R的原型 存在于 变量L的原型链上
function instance_of(L,R){
// 验证如果为基本数据类型,就直接返回false
const baseType = ['string', 'number','boolean','undefined','symbol']
if(baseType.includes(typeof(L))) {
return false }
let RP = R.prototype; //取 R 的显示原型
L = L.__proto__; //取 L 的隐式原型
while(true){
// 无线循环的写法(也可以使 for(;;) )
if(L === null){
//找到最顶层
return false;
}
if(L === RP){
//严格相等
return true;
}
L = L.__proto__; //没找到继续向上一层原型链查找
}
}
第三问:作用域安全的构造函数–当我们new一个构造函数的时候可以获得一个实例,要是我们忘记写new了呢?
例如
function Person(){
this.name = "小红"
}
p = Person();
这会发生什么问题?,怎么解决
这样直接使用,this会映射到全局对象window上。解决方法可以是:首先确认this对象是正确类型的实例。如果不是,那么会创建新的实例并返回。请看下面的例子
function Person(){
if(this instanceof Person){
this.name = "小红"
}else{
return new Person()
}
}
p = Person();
第四问:谈一下惰性载入函数
在JavaScript代码中,由于浏览器之间行为的差异,多数JavaScript代码包含了大量的if语句,以检查浏览器特性,解决不同浏览器的兼容问题。例如添加事件的函数:
function addEvent (element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
}
每次调用addEvent()的时候,都要对浏览器所支持的能力仔细检查。首先检查是否支持addEventListener方法,如果不支持再检查是否支持attachEvent方法,如果还不支持,就用DOM 0级的方法添加事件。在调用addEvent()过程中,每次这个过程都要走一遍。其实,浏览器支持其中的一种方法就会一直支持他,就没有必要再进行其他分支的检测了,也就是说if语句不必每次都执行,代码可以运行的更快一些。解决的方案称之为惰性载入。
所谓惰性载入,就是说函数的if分支只会执行一次,之后调用函数时,直接进入所支持的分支代码。有两种实现惰性载入的方式,第一种事函数在第一次调用时,对函数本身进行二次处理,该函数会被覆盖为符合分支条件的函数,这样对原函数的调用就不用再经过执行的分支了,我们可以用下面的方式使用惰性载入重写addEvent()。
function addEvent (type, element, handler) {
if (element.addEventListener) {
addEvent = function (type, element, handler) {
element.addEventListener(type, handler, false);
}
}
else if(element.attachEvent){
addEvent = function (type, element, handler) {
element.attachEvent('on' + type, handler);
}
}
else{
addEvent = function (type, element, handler) {
element['on' + type] = handler;
}
}
return addEvent(type, element, handler);
}
在这个惰性载入的addEvent()中,if语句的每个分支都会为addEvent变量赋值,有效覆盖了原函数。最后一步便是调用了新赋函数。下一次调用addEvent()的时候,便会直接调用新赋值的函数,这样就不用再执行if语句了。
第二种实现惰性载入的方式是在声明函数时就指定适当的函数。这样在第一次调用函数时就不会损失性能了,只在代码加载时会损失一点性能。一下就是按照这一思路重写的addEvent()。
var addEvent = (function () {
if (document.addEventListener) {
return function (type, element, fun) {
element.addEventListener(type, fun, false);
}
}
else if (document.attachEvent) {
return function (type, element, fun) {
element.attachEvent('on' + type, fun);
}
}
else {
return function (type, element, fun) {
element['on' + type] = fun;
}
}
})();
这个例子中使用的技巧是创建一个匿名的自执行函数,通过不同的分支以确定应该使用那个函数实现,实际的逻辑都一样,不一样的地方就是使用了函数表达式(使用了var定义函数)和新增了一个匿名函数,另外每个分支都返回一个正确的函数,并立即将其赋值给变量addEvent。
惰性载入函数的优点只执行一次if分支,避免了函数每次执行时候都要执行if分支和不必要的代码,因此提升了代码性能,至于那种方式更合适,就要看您的需求而定了。
第五问:谈一下函数节流
概念:限制一个函数在一定时间内只能执行一次。
主要实现思路 就是通过 setTimeout 定时器,通过设置延时时间,在第一次调用时,创建定时器,先设定一个变量true,写入需要执行的函数。第二次执行这个函数时,会判断变量是否true,是则返回。当第一次的定时器执行完函数最后会设定变量为false。那么下次判断变量时则为false,函数会依次运行。目的在于在一定的时间内,保证多次函数的请求只执行最后一次调用。
函数节流的代码实现
function throttle(fn,wait){
var timer = null;
return function(){
var context = this;
var args = arguments;
if(!timer){
timer = setTimeout(function(){
fn.apply(context,args);
timer = null;
},wait)
}
}
}
function handle(){
console.log(Math.random());
}
window.addEventListener("mousemove",throttle(handle,1000));
函数节流的应用场景(throttle)