(超详细版)2019北京面试题————JavaScript

10,574 阅读10分钟

1、JS判断数据类型的方法

① typeof:基本数据类型没有问题,引用数据类型有问题。
当变量是:number, string, boolean, function, undefined, object类型时,可以使用typeof进行判断。
当变量是arr, json, null, date, reg, error 类型时全部被错误的检测为object类型。
② instanceof:基本数据类型会有问题,而引用数据类型没有问题。

//基本数据类型
console.log("1" instanceof String); //false
console.log(1 instanceof Number);  //false
console.log(true instanceof Boolean);  //false
//引用数据类型
console.log([] instanceof Array);  //true
console.log(function(){} instanceof Function);  //true
console.log({} instanceof Object);  //true
③ constructor:除了undefined和null,其它变量都能使用constructor判断类型。

console.log(("1").constructor === String);  //true
console.log((1).constructor === Number);  //true
console.log((true).constructor === Boolean);  //true
console.log(([]).constructor === Array);  //true
console.log((function(){}).constructor === Function);  //true
console.log(({}).constructor === Object);  //true
console.log((null).constructor === Null);   //报错
console.log((undefined).constructor === Undefined);  //报错

声明了一个构造函数,并且把他的原型指向了Array的原型。

function Fn(){};
Fn.prototype=new Array(); 
var f=new Fn();
console.log(f.constructor===Fn);  //false
console.log(f.constructor===Array);  //true

原因:
1、array属于引用型数据,在传递过程中,仅仅是引用地址的传递。
2、每个页面的Array原生对象所引用的地址是不一样的,在子页面声明的array,所对应的构造函数,是子页面的Array对象;父页面来进行判断,使用的Array并不等于子页面的Array;切记,不然很难跟踪问题!
④ Object.prototype.toString.call();

console.log(Object.prototype.toString.call(1));  //[object Number]
console.log(Object.prototype.toString.call("1"));  //[object String]
console.log(Object.prototype.toString.call(true));  //[object Boolean]
console.log(Object.prototype.toString.call([]));  //[object Array]
console.log(Object.prototype.toString.call(function(){})); //[object Function]
console.log(Object.prototype.toString.call({}));  //[object Object]
console.log(Object.prototype.toString.call(null));  //[object Null]
console.log(Object.prototype.toString.call(undefined));  //[object Undefined]

⑤ jquery中的$.type()

2、定义函数的方式:

/第一种
function myFunction(){
    
}
//定义函数的字符串,函数名本质是变量名,指向某个function的引用。
console.log(myFunction);
//function
console.log(typeof myFunction)

//第二种
var myFunction = new Function(
    "num1"
    "num2"
    "var sum = num1 + num2;return sum"
)
console.log(myFunction(10,20))

3、数组排序:

var arr = [1,36,52,23,48,96,5];
//第一种:
function arrSort(a,b){
    return a - b;
}
console.log(arr.sort(arrSort));

//第二种:冒泡排序
//思想:让数组当中相邻的两个数进行比较,数组当中比较小的数值向下沉,数值比较大的向上浮!
//      外层for循环控制循环次数,内层for循环控制相邻的两个元素进行比较。
function bubbleSort(arr){
    for(var i = 0;i < arr.length-1;i++){
        for(var j = 0;j < arr.length-1-i;j++){
            if(arr[j] > arr[j+1]){
                swap(arr,j,j+1)
            }
        }
    }
    return arr;
}
function swap(arr,i,j){
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
console.log(bubbleSort(arr));

//第三种:选择排序
//思想:以起始元素为基准,再从剩余的未排序的元素中挨个与起始元素进行比较,找到剩余未排序元素中
//     最小的一个与之交换位置。重复此步骤。
function selectSort(arr){
    for(var i = 0;i < arr.length-1;i++){
        for(var j = i+1;j < arr.length;j++){
            if(arr[i] > arr[j]){
                awap(arr,i,j)
            }
        }
    }
    return arr;
}
function swap(arr,i,j){
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
console.log(selectSort(arr))

//第四种:选择排序2
function quickSort(arr){
    for(var i = 0;i < arr.length-1;i++){
        var num = arr[i];
        var index = i;
        for(var j = i+1;j < arr.length;j++){
            if(num > arr[j]){
                num = arr[j];
                index = j;
            }
         }
         if(index != i){
             swap(arr,i,index);
         }
      }
      return arr;
  }
  function swap(arr,i,j){
      var temp = arr[i];
      arr[i] = arr[j];
      arr[j] = temp;
  }
  var arr = [23,56,4,89,556,114,1];
  console.log(quickSort(arr));                      

//第五种:快速排序
//1、从数组中取出一个数作为基准。在原数组中进行移动,将大于基准的数放到基准的右边,小于基准的数放到
//    基准左边,在基准左右形成两个子数组。在左右子数组中反复执行步骤1、2。直到所有子数组只剩下一个数。
function quickSort(arr,i,j){
    if(i < j){
        let left = i;
        let right = j;
        let pivot = arr[left];
        while(i < j){
            while(arr[j] >= pivot && i < j){
                j--;
            }
            if(i < j){
                arr[i++] = arr[j];
            }
            while(arr[i] <= pivot && i < j){
                i++;
            }
            if(i < j){
                arr[j--] = arr[i]
            }
        }
        arr[i] = pivot;
        quickSort(arr,left,i-1);
        quickSort(arr,i+1,right);
        return arr;
    }
}
let arr = [23,56,4,89,556,114,1];
console.log(quickSort(arr,0,arr.length-1));

//快速排序(for循环)
function quickSort(arr){
    //如果数组的长度小于等于1,则直接返回这个数组
    if(arr.length <= 1){
        return arr;
    }
    //选择基准数(四舍五入)
    var pivotIndex = Math.floor(arr.length/2);
    //将基准数与原数组分离
    var pivot = arr.splice(pivotIndex,1)[0];
    var left = [];
    var right = [];
    for(var i = 0;i < arr.length;i++){
        if(arr[i] <= pivot){
            left.push(arr[i]);
        }else{
            right.push(arr[i]);
        }
    }
    return quickSort(left).concat(pivot,quickSort(right));
}
let arr = [23,56,4,89,556,114,1];
console.log(quickSort(arr));

4、深拷贝和浅拷贝?

浅拷贝:浅拷贝是会将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。
深拷贝:深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。 深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象。

深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝;

浅拷贝(只能拷贝一层):Object.assign和for in进行{ }和[ ]的拷贝。

//拷贝1层(测试)------------------------------------------------------------
//Object.assign()拷贝方法
    //拷贝{}
        a = {name:"张三"};
        b = Object.assign({},a);
        b.name = "李四";    //拷贝完之后进行改变属性,测试拷贝的属性值改变原数据是否改变。
        console.log(a,b);
        //输出:{name:"张三"} 
               {name:"李四"} (拷贝成功)
    //拷贝[]
        a = ["1","2","3"];
        b = Object.assign([],a);
        b[1]="hello";
        console.log(a,b)
        //输出:["1","2","3"] 
               ["1","hello","3"] (拷贝成功)

//for in拷贝方法
    var copy = function(a){
        var res = a.constructor();
        for(var key in a){
            if(a.hasOwnProperty(key)){
                res[key] = a[key]
            }
        }
        return res;
    }   
    a = {name:"123",people:{name:"456"}};
    b = copy(a);
    b.people.name="hello"; 
    a = ["a","b","c"];b = copy(a);
    b[1] = 0;//拷贝完之后进行改变属性,测试拷贝的属性值改变原数据是否改变。
    console.log(a,b)  
    //输出:["a","b","c"] 
           ["a","0","c"] (拷贝成功)
//拷贝2层(测试)-------------------------------------------------------------
    //Object.assign()拷贝方法
        a = {name:"abc",people:{name:"张三"}};
        b = Object.assign({},a);
        b.people.name="李四"; 
        console.log(a,b)    
        //输出:{name:"abc",people:{name:"李四"}} 
               {name:"abc",people:{name:"李四"}} (拷贝失败)    //for in拷贝方法
         var copy = function(a){
             var res = a.constructor();
             console.log(res);
             for(var key in a){
                 if(a.hasOwnProperty(key)){
                    res[key] = a[key]
                 }
             }
         return res;
        }       
        a = ["a","b",{name:"张三"}];b = copy(a);b[2].name="李四";
        console.log(a,b)   
        //输出:{name:"abc",people:{name:"李四"}} 
               {name:"abc",people:{name:"李四"}} (拷贝失败)

constructor( ) 是一种用于创建和初始化class创建的对象的特殊方法。
hasOwnProperty( ) 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键);
深拷贝最简单的实现是:JSON.parse ( JSON.stringify ( obj ) ),同时有一定的缺陷:
① 对象的属性值是函数时,无法拷贝
② 原型链上的属性无法拷贝
③ 不能正确的处理Data类型的数据
④ 不能处理RegExp
⑤ 会忽略symbol、undefined

//JSON.parse(JSON.stringify(obj))方法拷贝2层
    var deepCopy = function(a){
        return JSON.parse(JSON.stringify(a));
    }
    var a = {name:"aaa",people:{name:"abc"}};
    var b = deepCopy(a);
    b.people.name = "def";
    console.log(a,b)   
    //输出:{name:"aaa",people:{name:"abc"}} 
           {name:"aaa",people:{name:"def"}} (拷贝成功)//JSON.parse(JSON.stringify(obj))方法拷贝3层
    var deepCopy = function(a){
        return JSON.parse(JSON.stringify(a))
    }
    var a = [1,2,{name:"aaa"}];
    var b = deepCopy(a);
    b[2].name = "bbb";
    console.log(a,b);   
    //输出:["1","2",{name:"aaa"}]
           ["1","2",{name:"bbb"}] (拷贝成功)
//JSON.parse(JSON.stringify(obj))拷贝函数的时候
    var deepCopy = function(a){
        return JSON.parse(JSON.stringify(a));
    }
    var a = {name:"aaa",fun:function(){console.log(1)},age:undefined};
    var b = deep(a);
    b.name = "bbb"
    console.log(a,b);   
    //输出:{name:"aaa",fun:function(){console.log(1)},age:undefined};
           {name:"bbb"} (拷贝失败,只拷贝到了name属性)  
//JSON.parse(JSON.stringify(obj))拷贝原型链上的属性
    function Person(name){
        this.name=name;
    }
    var a = new Person("Bob");
    var b = deep(a);
    console.log(a.constructor == Person);   //true
    console.log(b.constructor == Object);   //true
    //先不说拷贝出的值,光是数据类型已经不同了 (拷贝失败)
    console.log(a,b)    
    //输出:
    // Person{name:"Bob"}    {name:"Bob"}

注意:
上述方法会忽略值为function以及undefined的字段,而且对data类型的支持也不太友好。
上述方法只能克隆原始对象自身的值,不能克隆他继承的值。

深拷贝(完美拷贝):

① 如果是基本数据类型,直接返回;
② 如果是RegExp或者Data类型,返回对应类型;
③ 如果是复杂数据类型,递归;
④ 考虑循环引用的问题。

function deepClone(obj,hash = new WeakMap()){   //递归拷贝
     if(obj instanceof RegExp) return new RegExp(obj);
     if(obj instanceof Date) return new Date(obj);
     if(obj === null || typeof obj !== 'object'){
         //如果不是复杂数据类型,直接返回
        return obj;
     }
     if(hash.has(obj)){
        return hash.get(obj);
     }
     //如果obj是数组,那么 obj.constructor 是 [Function: Array]
     //如果obj是对象,那么 obj.constructor 是 [Function: Object]
    let t = new obj.constructor();
    hash.set(obj,t);
    for(let key in obj){
        //递归
        if(obj.hasOwnProperty(key)){    //是否是自身的属性
            t[key] = deepClone(obj[key],hash);
        } 
     }
     return t;
}   
var show = {
    name:"Bob",
    fun:function(){console.log(1)},
    age:null,
    pic:undefined,
}
var show2 = deepClone(show);
show2.name="Mary"
console.log(show,show2)   //拷贝成功,完美拷贝 

浅拷贝和深拷贝的区别:
浅拷贝只能复制对象或数组的第一层属性,而深拷贝是拷贝多层,每一级的数据都会被拷贝出来。

5、跨域(怎么解决跨域问题)(跨域是什么)(为什么会有跨域):

       造成跨域的原因就是浏览器的同源策略:只要满足协议主机端口一致,则两个页面具有相同的源。同源策略限制了从同一个源加载的文档或脚本如何来自另一个源的资源进行交互,这是一个用于隔离潜在恶意文件的重要安全机制。(直白:我在一个域名地址下的网页,如果请求一个受到同源策略限制的地址接口的时候,就会报错,这是为了在我的网页下请求别的地址的文件可能是恶意代码,造成不必要的问题。)
解决方法:
      ① jsonp,允许script加载第三方资源。jsonp是一种非正式的传输协议,该协议的一个要点就是允许用户传递一个callback函数给服务端,然后服务端返回数据的时候会将json数据包裹在这个callback函数中返回。jsonp的本质是利用script标签的src熟悉进行跨域请求,但只能用于get请求。
      ② 反向代理 (nginx 服务内部配置 Access-Control-Allow-Origin *);
      ③ cors 前后端协作设置请求头部,Access-Control-Allow-Origin 等头部信息
      ④ iframe 嵌套通讯 (可以与下面的postmessage一起使用)
      ⑤ window.postmessage ( ),该方法的使用须与iframe嵌套使用。

6、为什么会有同源策略?

JavaScript访问资源,处于安全方面考虑,要限制JS的访问能力,不允许跨域访问资源。如果没有同源限制存在浏览器中的cookie等其他数据可以任意读取,不同域下DOM任意操作,ajax任意请求的话如果浏览了恶意网站那么就会泄漏这些隐私数据。
(再次强调)同源策略:同协议、同域名、同端口。

7、原型、原型链、构造函数、实例、继承?

原型(__proto__):每个对象都有__proto__属性,__proto__指向创建他的构造函数的原型对象(prototype)。
原型链:凡是对象都有一个原型,通过__proto__可以访问原型,访问的原型又是对象,这样依次下去,就会构成一个对象的序列,该结构成为原型链。
(简单明了说法:一个原型对象是另一个原型对象的实例,相关的原型对象层层递进,就构成了实例与原型的链条,就是原型链。当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链,当然如果最终找不到返回null。)
构造函数:
① 构造函数的首字母必须大写,用来区分于普通函数;
② 内部使用的this对象,来指向即将要生成的实例对象;
③ 使用new来生成实例对象。

继承:强烈推荐各位去这个网站 github.com/YvetteLau/B…

8、arguments的解释?

       arguments是一个类似于数组的对象,对应于传递给函数的参数,他有length属性,可以arguments[ i ]来访问对象中的元素,但是它不能用数组的一些方法。例如push、pop、slice等。arguments虽然不是一个数组,但是它可以转成一个真正的数组。

function argText(a,b,c){
    var actual = arguments.length;   //实际传参个数
    var hope = argText.length   //期望传参个数
    console.log(actual,hope);
    //转换数组:
    var args = [].slice.call(arguments);   //第一种
    var args = Array.prototype.slice.call(arguments);   //第二种
    let args = Array.from(arguments);   //第三种
    let args = [...arguments];   //第四种
    console.log(args)
}
argText(1,2)
//输出: 2 3   

        每一个函数都会有一个Arguments对象实例arguments,它引用着函数的实参,可以用数组下标的方式" [ ] "引用arguments的元素。arguments.length为函数实参个数,arguments.callee引用函数自身。
       arguments对象是所有函数中可用的局部变量,你可以使用arguments对象在函数中引用函数的参数,此参数包含传递给函数的每个参数条目。
arguments.callee:Arguments的callee属性可以调用函数本身,当函数正在执行时才可调用,可以实现方法的递归调用。

function argText(){
    var e = arguments.callee.toString();
    console.log(e);
}
argText(); 

arguments.caller:指向调用当前函数的函数

function argText(){
    if(argText.caller){
        var caller = argText.caller.toString();
        console.log(caller);
    }else{
        console.log("no caller");
    }  
}
function handler(){
    argText();
}
function copyHandler(){
    handler();
}
argText()
//输出: no caller
handler()
//输出: function handler(){argText();}
copyHandler();     
//输出: function handler(){argText();}   

arguments方法重载(可以了解):blog.xieliqun.com/2016/08/14/…

9、作用域链、闭包、作用域;

⑴ 作用域链
       定义:一个函数在访问变量的时候,优先使用自己的局部变量,如果没有这个变量的申明,则向上级访问,一直访问到全局。全局都没有的话,语法错误:is not defined。
⑵闭包closure
       定义:当一个函数的返回值是另外一个函数,而返回的那个函数如果调用了其父函数的内部变量,且返回的那个函数在外部被执行,就产生了闭包.闭包是一个环境,具体指的就是外部函数--高阶函数
        闭包的特性:
            ①函数嵌套函数;
            ②内部函数可以直接访问外部函数的内部变量或参数;
            ③变量或参数不会被垃圾回收机制回收。
        闭包的优点:
            ①变量长期驻扎在内存中;
            ②避免全局变量的污染;
            ③私有成员的存在。
       闭包的缺点: 常驻内存,增大内存的使用量,使用不当会造成内存泄漏。
⑶作用域:
       全局作用域:window。
       局部作用域:函数内部定义的。

//使用闭包找到dome元素的下标
var oLi = document.getElementsByTagName("li");
// 使用闭包后点击对应的li标签,会返回对应的下标
for(var i = 0;i < oLi.length;i++){ 
    //闭包方式一
    (function(j){
        oLi[j].onclick = function(){
            console.log(j)
        }
    })(i);
    //闭包方式二
    oLi[i].onclick = function(index){
        return function(){
            console.log(index);
        }
    }(i);
    //不使用闭包的情况下,输出值全部为(length+1)
    oLi[i].onclick = function(){
        console.log(i);
    }
}

10、ES3~5:

ES3~5数组常见的方法:
1、concat( ):数组合并。
2、join( ):数组转字符串。
3、pop( ):删除最后一个元素。
4、push( ):数组向后添加。
5、unshift( ):数组向前添加。
6、reverse( ):数组翻转。
7、shift( ):删除第一个元素。
8、slice( ):数组元素的截取,返回一个新数组,新数组是截取的元素,可以为负值。
9、sort( ):对数组元素进行排序;
10、splice( ):删除元素,并向数组添加新元素;
11、toString( ):数组转字符串;
12、toLocaleString( ):将数组转换为本地数组。
13、forEach( ):数组进行遍历;
14、map( ):没有return时,对数组的遍历。有return时,返回一个新数组,该新数组的元素是经过过滤(逻辑处理)过的函数。
15、filter( ):筛选。
16、every( ):当数组中每一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
17、some( ):当数组中有一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
18、reduce( ):回调函数中有4个参数。prev(之前计算过的值),next(之前计算过的下一个的值),index,arr。把数组列表计算成一个单一值 。

//1、every()
var arr = [1,56,80,5];
var main = arr.every(n => n > 0);
console.log(main)   //输出:true

//2、some()
var arr = [1,-56,80,-5];
var main = arr.some(n => n > 0);
console.log(main)    //输出:true

//3、reducer()
var arr = [10,20,30,40]
let result = arr.reduce(function(prev,next,index,arr){
    return prev + next;
})
console.log(result);  //输出:100

ES3~5字符串常见的方法:

1、chartAt( ):返回在指定位置的字符;
2、concat( ):字符串连接;
3、indexOf( ):检索字符串,找不到返回-1;
4、slice( ):提取字符串片段,并在新的字符串中返回被提取的部分;
5、split( ):字符串转数组;
6、substr( ):从起始索引号提取字符串中指定数目的字符;
7、substring( ):提取字符串中两个指定的索引号之间的字符;
8、toLowerCase( ):字符串转小写;
9、toUpperCase( ):字符串转大写;
10、valueOf( ):返回某个字符串对象的原始值; 
11、trim( ):删除字符串两边的空格;

11、ES6

ES6数组的常用方法:
1、Array.from( ):将对象或字符串转成数组,注意得有length。
2、Array.of( ): 将一组值转换为数组。
3、copyWithin(target,start(可选),end(可选)):数组内数据的复制替换
target:从该位置开始替换数据;
start:从该位置开始读取数据,默认为0;
end:到该位置停止数据的读取,默认为数组的长度
4、find( ):用于找出第一个符合条件的数组成员。
5、findIndex( ):返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1。
6、fill(value,start,end):使用给定值,填充一个数组。
value:填充的值;
start:开始填充的位置;
end:填充结束的位置。
7、keys( ):对键名的遍历。
8、values( ):对键值的遍历。
9、entries( ):对键值对的遍历。
10、includes( ):数组原型的方法,查找一个数值是否在数组中,只能判断一些简单类型的数据,对于复杂类型的数据无法判断。该方法接受两个参数,分别是查询的数据和初始的查询索引值。
11、flat( ):用于数组扁平,数组去除未定义。
12、flatMap( ):对原数组的每个成员执行一个函数。
13、Map( ):是一组键值对的结构,具有极快的查找速度。
14、Set( ):Set和Map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set中,没有重复的key。

//1、Array.from()  --   Array.of()
    var  arrayLink = {
        "0":"a",
        "1":"b",
        "2":"c",
        length:3
    }
    var arr = Array.from(arrayLink)
    console.log(arr)   // 输出: [a,b,c]
    console.log(Array.from("abcdefg"))  //输出:["a", "b", "c", "d", "e", "f", "g"]
    console.log(Array.of(1,2,3,4,5))  //输出: [1, 2, 3, 4, 5]

//2、copyWithin()
    var arr = [1,2,3,4,5];
    var main = arr.copyWithin(0,3);
    console.log(main);   //输出:[4,5,3,4,5]

//3、find()
    var arr = [1,-5,2,9,-6];
    var main = arr.find(n =>  n < 0);
    console.log(main);   //输出:-5

//4、fill()
    var arr = ["a","b","c","d"];
    console.log(arr.fill(7,1,2));//输出:["a",7,"c","d"]  

//5、keys()  values()  entries()
    var arr = ["a","b","c","d"];
    for(let index of arr.keys()){
	    console.log(index);
    }
    for(let elem of arr.values()){
	    console.log(elem);
    }
    for(let [index,elem] of arr.entries()){
	    console.log(index,elem);
    }  

//6、includes()
    let arr = [12,34,223,45,67]
    console.log(arr.includes(45))   //输出:true
    [1, 2, NaN].includes(NaN)     // true
    [1, 2, NaN].indexOf(NaN)      // -1

//7、Map
    var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
    m.get('Michael'); // 95
    //初始化Map需要一个二维数组,或者直接初始化一个空Map。Map具有以下方法:
    var m = new Map(); // 空Map
    m.set('Adam', 67); // 添加新的key-value
    m.set('Bob', 59);
    m.has('Adam'); // 是否存在key 'Adam': true
    m.get('Adam'); // 67
    m.delete('Adam'); // 删除key 'Adam'
    m.get('Adam'); // undefined
    //由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
    var m = new Map();
    m.set('Adam', 67);
    m.set('Adam', 88);
    m.get('Adam'); // 88

//8、Set
    //要创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:
    var s1 = new Set(); // 空Set
    var s2 = new Set([1, 2, 3]); // 含1, 2, 3
    //重复元素在Set中自动被过滤:
    var s = new Set([1, 2, 3, 3, '3']);
    s; // Set {1, 2, 3, "3"}  注意:数字3和字符串'3'是不同的元素
    //通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果:
    s.add(4);
    s; // Set {1, 2, 3, 4}
    s.add(4);
    s; // 仍然是 Set {1, 2, 3, 4}
    //通过delete(key)方法可以删除元素:
    var s = new Set([1, 2, 3]);
    s; // Set {1, 2, 3}
    s.delete(3);
    s; // Set {1, 2}

ES6字符串的常用方法:
1、for···of:遍历
2、模板字符串:` `

ES6新增数据类型:Symbol

ES10新增数据类型BigInt:BigInt数据类型的目的是比Number数据类型支持的范围更大的整数值

12、ES6解构赋值

①数组的解构赋值:

let [a,b,c,d] = [1,2,3,4];
console.log(a,b,c,d);

let [x,y,...z] = [1,2,3,4,5,6,7,8];
console.log(x,y,z);

let arr = [1,2,3,4,5];
console.log(...arr);   //输出:1 2 3 4 5 

let [, , third] = ["foo","bar","baz"];
console.log(third);   //输出: baz

let [x,y,...z] = ["a"];
console.log(x,y,z);   //输出:"a" undefined []

//报错:
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

let [foo = true] = [];
console.log(foo) //输出: true

let [x = 1] = [undefined];
let [y = 1] = [null];
console.log(x)   //输出: 1
console.log(y)   //输出: 1

②对象的解构赋值:
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

let {foo,bar} = {bar:"acb",foo:"dce"};
console.log(foo,bar);    //输出:dce abc

let {baz} = {foo:"aaa",bar:"bbb"};
console.log(baz);   //输出:undefined

let {foo:baz} = {foo:"aaa",bar:"bbb"};
console.log(baz);   //输出:aaa
console.log(foo);   //输出:报错

let x;
{x} = {x:1}   //输出:报错

let x;
({x} = {x:1});
console.log(x)    //输出:1

③字符串的解构赋值:

const [a,b,c,d,e] = "hello";
console.log(a,b,c,d,e)   //输出:h e l l o

let {length : len} = "hello";
console.log(len);    //输出: 5

④函数参数的解构赋值:

function add([x,y]){
    return x + y;
}
console.log(add([1,2]));   //输出:3

       (重点)解构赋值的用处:

用处1:交换两个变量的值

let a = 10,b = 20;
console.log([a,b] = [b,a]);

用处2:从函数返回多个值

function fn(){
    return [1,2,3,4,5];
}
var [a,b,c,d,e] = fn();
console.log(a,b,c,d,e);

用处3:函数参数的定义

function fn3([x,y,z]){
    return x+y+z;
}
console.log(fn3([4,5,6]))

用处4:函数参数的默认值

function fn4([x=1,y=2,z=3]){
    return x+y+z;
}    
console.log(fn4([4,5,6]))

用处5:提取JSON数据

var dataJson = {
    "id":1,
    "status":"ok",
    "data":[1,2,3,4]
}    
var {id,status,data:arr} = dataJson;
console.log(id,status,arr);
//输出: 1 "ok" [1,2,3,4]

用处6:遍历Set、Map数据结构

var map = new Map();
map.set("first","hello");
map.set("second","world");
console.log(map);
for(var [key,value] of map){
    console.log(key+"is:"+value)
}

用处7:输入模块的指定方法

var {sourceSort,sourceNumber} = require("soure-map")

13、数组去重

var arr = [1,2,45,44,45,2,89,1,1,2,1,2];

第一种:new Set()

var box = Array.from(new Set(arr));
var box = [...new Set(arr)];
console.log(box);

第二种:indexOf()

var box = [];
for(var i = 0;i < arr.length;i++){
	if(box.indexOf(arr[i]) == -1){
		box.push(arr[i])
	}
}
console.log(box);

第三种:splice()

for(var i = 0;i < arr.length;i++){
	for(var j = i+1; j < arr.length;j++){
		if(arr[i] == arr[j]){
			arr.splice(j,1);
		}
	}
}
console.log(arr);

第四种:sort()+splice()

arr.sort();
for(var i = 0; i < arr.length;i++){
	if(arr[i] == arr[i+1]){
		arr.splice(i,1);
		i--;
	}
}	
console.log(arr);

第五种:递归函数

Array.prototype.unique = function(){
	var arr = this;
	len = arr.length;
	arr.sort(function(a,b){
		return a - b; 
	})
	function loop(index){
		if(index >= 1){
			if(arr[index] === arr[index-1]){
				arr.splice(index,1);
			}
			loop(index-1);
		}
	}
	loop(len-1);
	return arr;
}
var b = arr.unique();
console.log(b);

简单写法如上,还有很多写法,在此不一一列举。

14、ES7新增(不定时添加)

① 求幂运算符(**)

var x = 5 ** 3;   //  5的三次方
console.log(x);   //输出: 125

② async、await异步解决方案:async函数返回的是一个 Promise 对象,可以使用 then 方法添加回调函数,async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。await是等待的意思,它等待的是promise 对象的执行完毕,并返回Promise 对象

15、ES8新增(不定时添加)

① Object.entries( ):该方法会将某个对象的可枚举属性与值按照二维数组的方式返回。(如果目标对象是数组,则会将数组的下标作为键值返回)

var obj = {
	name:"Bob",
	age:25
}
var arr = Object.entries(obj)
console.log(arr);   //输出:[['name', 'Bob'], ['age', 25]]

var arr = ["Bob","Tom"];
var Create = Object.entries(arr);
console.log(Create);   //输出:[['0', 'Bob'], ['1', 'Tom']]

② Object.values():它的工作原理和Object.entries()方法很像,但是它只返回键值对中的值,结果是一维数组。

var obj = {
    	name:"Bob",
    	age:25
}
var arr = Object.values(obj);
console.log(arr);   //输出:["Bob", 25]

var obj = {
    	2:"a",
	1:"b",
	3:"c"
}
var arr = Object.values(obj);
console.log(arr);   //输出:["b", "a", "c"]

var obj = [1,3];
var arr = Object.values(obj);
console.log(arr);   //输出:[1,3]

③字符串填充padStart()、padEnd():字符串填充方法,该方法可以使得字符串达到固定长度。它有两个参数,字符串目标长度和填充内容。

var str = "create"
var newStr1 = str.padStart(10,"x");
var newStr3 = str.padStart(6,"x");
var newStr2 = str.padEnd(10,"x");
var newStr4 = str.padEnd(6,"x");
console.log(newStr1,newStr2,newStr3,newStr4)
//输出:xxxxcreate createxxxx create create

④ Object.assign():方法用于对象的合并。

var obj1 = {name:"Bob"};
var obj2 = {age:25};
var newObj = Object.assign(obj1,obj2);
console.log(newObj);   //输出: {name: "Bob", age: 25}

16、总结异步编程的6种方式:

① 回调函数;
② 事件监听;
③ 发布订阅模式;
④ Promise;
⑤ Generator(ES6);
⑥ async。

//Generator函数:
function* add(){
	yield "1";
	yield "2";
	yield "3";
	reutrn;
}
var h = add();
console.log(h.next());   //输出: {value:"1",done:false}
console.log(h.next());  //输出: {value:"2",done:false}
console.log(h.next());  //输出: {value:"3",done:false}
console.log(h.next());  //输出: 报错
//如果去掉return  则,输出:{value: undefined, done: true}

(强烈推荐)原文链接:mp.weixin.qq.com/s/TY6LbYQDy…

17、设计模式(下面只列举了常用10大)

推荐学会前4种,了解后6种

① 工厂模式:去做同样的事情,实现同样的效果,解决多个相似的问题这时候需要使用工厂模式

function CreatePerson(){
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sayName = function(){
        return this.name;
    }
    return obj;
}
var p1 = new CreatePerson("Bob",28);
var p2 = new CreatePerson("Tom",25);

② 单体模式:一个用来划分命名空间并将一批属性和方法组织在一起的对象,如果它可以被实例化,那么它只能被实例化一次。

var Singleton = function(name){    //单体模式
    this.name = name;
    this.instance = null;
}
Singleton.prototype.getName = function(){
    return this.name;
}
function getInstance(name){   //获取实例对象
    if(!this.instance){
        this.instance = new Singleton(name);
    }
    return this.instance;
}
//测试单体模式
var a = getInstance("Bob");
var b = getInstance("Tom");
console.log(a);   //输出:Singleton {name: "Bob", instance: null}
console.log(b);   //输出:Singleton {name: "Bob", instance: null}
console.log(a === b);   //输出:true

③ 模块模式:以对象字面量的方式来创建单体模式,为单体模式添加私有变量和私有方法能够减少全局变量的使用。使用场景:如果我们必须创建一个对象并以某些数据进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么我们这个时候就可以使用模块模式了。

var singleMode = (function(){
    var privateNum = 112;   //创建私有变量
    function privateFunc(){
        //实现自己业务逻辑的代码
    }
    //返回一个对象包含公有方法和属性
    return {
        publicMethod1:publicMethod1,
        publicMethod2:publicMethos1,
    }
})()

④ 代理模式:代理是一个对象,它可以用来控制对本体对象的访问,它与本体对象实现了同样的接口,代理对象会把所有的调用方法传递给本体对象的;代理模式最基本的形式是对访问进行控制,而本体对象则负责执行所分派的那个对象的函数或者类,简单的来讲本地对象注重的去执行页面上的代码,代理则控制本地对象何时被实例化,何时被使用;

//比如现在京东ceo想送给奶茶妹一个礼物,但是呢假如该ceo不好意思送,或者由于工作忙没有时间送,
//那么这个时候他就想委托他的经纪人去做这件事
var TeaAndMilkGirl = function(name) {  // 先申明一个奶茶妹对象
    this.name = name;
};
var Ceo = function(girl) {   // 这是京东ceo先生
    this.girl = girl;
    this.sendMarriageRing = function(ring) {   // 送结婚礼物 给奶茶妹
        console.log("Hi " + this.girl.name + ", ceo送你一个礼物:" + ring);
    }
};
var ProxyObj = function(girl){   // 京东ceo的经纪人是代理,来代替送
    this.girl = girl;
    this.sendGift = function(gift) {   // 经纪人代理送礼物给奶茶妹
        (new Ceo(this.girl)).sendMarriageRing(gift);   // 代理模式负责本体对象实例化
    }
};
// 初始化
var proxy = new ProxyObj(new TeaAndMilkGirl("奶茶妹"));
proxy.sendGift("结婚戒"); // Hi 奶茶妹, ceo送你一个礼物:结婚戒

⑤ 职责链模式: 消除请求的发送者与接收者之间的耦合。职责链是由多个不同的对象组成的,发送者是发送请求的对象,而接收者则是链中那些接收这种请求并且对其进行处理或传递的对象。请求本身有时候也可以是一个对象,它封装了和操作有关的所有数据。
⑥ 命令模式:命令模式中的命令指的是一个执行某些特定事情的指令。命令模式使用的场景有:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道请求的操作是什么,此时希望用一种松耦合的方式来设计程序代码;使得请求发送者和请求接受者消除彼此代码中的耦合关系。
⑦ 模板方法模式:模板方法模式由二部分组成,第一部分是抽象父类,第二部分是具体实现的子类,一般的情况下是抽象父类封装了子类的算法框架,包括实现一些公共方法及封装子类中所有方法的执行顺序,子类可以继承这个父类,并且可以在子类中重写父类的方法,从而实现自己的业务逻辑。
⑧ 策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
优点:1. 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句。
          2. 策略模式提供了开放-封闭原则,使代码更容易理解和扩展。
          3. 策略模式中的代码可以复用。
⑨ 发布-订阅者模式:发布---订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。
⑩ 中介者模式:中介者模式的作用是解除对象与对象之间的耦合关系,增加一个中介对象后,所有的相关对象都通过中介者对象来通信,而不是相互引用,所以当一个对象发送改变时,只需要通知中介者对象即可。中介者使各个对象之间耦合松散,而且可以独立地改变它们之间的交互。

参考网站:blog.csdn.net/song_mou_xi…

18、Ajax的原生写法:

var xhr;    //创建ajax对象
if(window.XMLHttpRequest){   //兼容IE
    xhr = new XMLHttpRequest();
}else{
    xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.open("get",url,true);   //建立连接
xhr.send();   //发送
xhr.onreadystatechange = function(){  //获取数据
    if(xhr.readyState == 4 && xhr.status == 200){
        var data = JSON.parse(xhr.responseText);
    }
}

19、图片的懒加载、预加载:

原理:
       预加载原理:就是在网页全部加载之前,提前加载图片,当用户需要查看时可直接从本地缓存中渲染,以提供给用户更好的体验,减少等待的时间。
       图片懒加载原理(缓载):通过监听onscroll事件判断资源位置,延迟加载图片或符合某些条件时才加载某些图片。首先为所有懒加载的静态资源添加自定义属性字段,比如如果是图片,可以指定data-src为真实的图片地址,src指向loading的图片。 然后当资源进入视口的时候,将src属性值替换成data-src的值。 可以使用元素的getBoundingRect().top判断是否在视口内,也可以使用元素距离文档顶部的距离offsetTop和scrollTop是否小于视口高度来判断 

//懒加载简单代码实现
//《JS代码》
window.onload = function(){
		//获取当前浏览器视口高度
		var viewHeight = document.documentElement.clientHeight;
		console.log(viewHeight);
		//鼠标滚动回调
		function lazyload(){
			var img = document.getElementsByClassName("img");
			for(let item of img){
				//获取每张图片距离顶部的距离
				var imgHeight = item.getBoundingClientRect();
				console.log(imgHeight)
				//判断当图片出现在视口160px的时候把地址放入src中,显示出图片
				if(imgHeight.top < (viewHeight - 10)){
					item.src = item.getAttribute("data-original");
				}
			}
		}
		lazyload(); //页面加载时把当前视口中的图片加载进来
		document.addEventListener("scroll",lazyload);
	}
//《HTML代码》
<img class="img" lazyload="true" data-origin="图片http地址">
<img class="img" lazyload="true" data-origin="图片http地址"><img class="img" lazyload="true" data-origin="图片http地址"><img class="img" lazyload="true" data-origin="图片http地址"><img class="img" lazyload="true" data-origin="图片http地址">

//预加载简单代码实现
//style
#box{width:0;height:30px;background:red;transition:1s;}
//html
<div id="box"></div>
<ul>
    <li><img src="图片http地址"></li>
    <li><img src="图片http地址"></li>
    <li><img src="图片http地址"></li>    <li><img src="图片http地址"></li>
    <li><img src="图片http地址"></li>    <li><img src="图片http地址"></li>    <li><img src="图片http地址"></li>    <li><img src="图片http地址"></li>
</ul>
//JS代码
window.onload = function(){
	var oBox = document.getElementById("box");
	var arr = [
		"https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3916481728,2850933383&fm=26&gp=0.jpg",
		"https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=291222564,3369340489&fm=26&gp=0.jpg",
		"https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3974834430,2578081919&fm=26&gp=0.jpg",
		"http://img3.imgtn.bdimg.com/it/u=2332284096,307149879&fm=26&gp=0.jpg",
		"http://img0.imgtn.bdimg.com/it/u=2667939876,1138687338&fm=26&gp=0.jpg",
		"http://img0.imgtn.bdimg.com/it/u=2981065320,3294183154&fm=26&gp=0.jpg",
		"http://img4.imgtn.bdimg.com/it/u=2647953258,2919321872&fm=26&gp=0.jpg",
		"http://img1.imgtn.bdimg.com/it/u=221171515,1209752772&fm=26&gp=0.jpg"
	]
	var num = 0;
	for(var i = 0; i < arr.length;i++ ){
		var oImg = new Image();  //新建一个图片对象
		console.log(oImg);
		oImg.onload = function(){
			num++;  //图片预加载完成,执行onload里面的代码
			oBox.style.width = num/arr.length * 100 + "%"; //通过图片的加载顺序,来控制进度条的宽度
		}
		oImg.src = arr[i];  //给图片对象添加路径,注意这条代码必须加在onload后面
	}
}

20、防抖、节流

防抖:所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
节流:所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。
两种方式可以实现,分别是时间戳版和定时器版。

//节流
// 思路:在规定时间内只触发一次
function throttle (fn, delay) {
  // 利用闭包保存时间
  let prev = Date.now()
  return function () {
    let context = this
    let arg = arguments
    let now = Date.now()
    if (now - prev >= delay) {
      fn.apply(context, arg)
      prev = Date.now()
    }
  }
}

function fn () {
  console.log('节流')
}
addEventListener('scroll', throttle(fn, 1000))

//防抖
// 思路:在规定时间内未触发第二次,则执行
function debounce (fn, delay) {
  // 利用闭包保存定时器
  let timer = null
  return function () {
    let context = this
    let arg = arguments
    // 在规定时间内再次触发会先清除定时器后再重设定时器
    clearTimeout(timer)
    timer = setTimeout(function () {
      fn.apply(context, arg)
    }, delay)
  }
}

function fn () {
  console.log('防抖')
}
addEventListener('scroll', debounce(fn, 1000))

21、页面加载进度条的实现

①定时器加载(原理):设置固定的时间后将遮罩层和加载图片隐藏,显示页面内容。
②通过加载状态事件实现进度条(原理):document.onreadystatechange(页面加载状态改变时的事件),document.readyState(返回当前文档的状态);
③通过CSS3来实现进度条;
④实时获取加载数据的进度条(原理):通过加载图像来实现效果。
⑤根据加载进度来改变进度条的长度(width值);
⑥根据文件的加载顺序来实现加载进度条。

22、this关键字(指向)

this是JavaScript语言的一个关键字,它是函数运行时,在函数体内部自动生成一个对象,只能在函数体内部使用。函数的不同使用场合,this有不同的值。总的来说this就是函数运行时所在的环境对象。

情况一:纯粹的函数调用:这是函数的最通常的用法,属于全局调用,因此this就代表全局对象

var x = 1;
function test(){
    console.log(this.x);
}
test();   // 1  情况二:作为对象方法

情况二:作为对象方法的调用:函数还可以作为某个对象的方法调用,这时this就指这个上级对象。

function test(){
    console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m();

情况三:作为构造函数调用:所谓构造函数,就是通过这个函数,可以生成一个新对象。这时this就指这个新对象。

function test(){
    this.x = 1;
}
var obj = new test();
console.log(obj.x);   // 1

情况四:apply的调用:apply( )是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此这时this指的就是这第一个参数。
apply( )的参数为空时,默认调用全局对象。这时运行结果为0,证明this指的是全局对象。

var x = 0;
function test(){
    console.log(this.x);
}
var obj = {};
obj.x = 1;
obj.m = test;
obj.m.apply();    // 0   this指的是全局对象
obj.m.apply(obj);   // 1    this指的是obj

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

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

情况六:箭头函数:箭头函数没有自己的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

23、对象和面向对象

对象:属性和方法的集合叫做对象(万物皆对象)。
面向对象:首先就是找对象,如果该对象不具备所需要的方法或属性,那就给它添加。面向对象是一种编程思维的改变。通过原型的方式来实现面向对象编程。
创建对象的方式(4种):new Object、字面量、构造函数、原型。

24、函数式编程

含义:函数式编程是一种强调以函数为主的软件开发风格。通过组合纯函数,避免共享状态、可变作用和副作用来构建软件的过程。
目的:使用函数来抽象作用在数据之上的控制流和操作,从而在系统中消除副作用并减少对状态的改变。
特点:函数式编程是声明性的而不是命令式的,应用状态流经纯函数中。相比于面向对象编程,其中的应用状态经常是共享的,并且和方法一起定义在一些对象中。

25、怎么判断两个对象是否相等?

① 首先比较两个对象的长度,如果长度不相等使flag为false,为不相等;
② 如果长度相等那就遍历对象1(对象2也可以),利用hasOwnProperty()方法查看对象1中是否包含对象2中的属性或者方法,如果不包含则使flag为false,为不想等。
③ 接下来判断两对象的内存地址是否相同,不同则为true

function compreObj(obj1, obj2) {
    var flag = true;
    function compre(obj1, obj2) {
        if (Object.keys(obj1).length != Object.keys(obj2).length) {
            flag = false;
        } else {
            for (let x in obj1) {
                if (obj2.hasOwnProperty(x)) {
                    if (obj1[x] !== obj2[x]) {
                        compre(obj1[x], obj2[x]);
                    }
                } else {
                    flag = false;
                }
            }
        }
        if (flag === false) {
            return false;
        } else {
            return true;
        }
    }
    return compre(obj1, obj2)
}
console.log(compreObj(对象1, 对象2));

26、事件模型:事件委托、代理?如何让事件先冒泡后捕获?

事件委托:又叫事件代理,利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
原理:事件冒泡机制,从最深的节点开始,然后逐步向上传播事件。
作用:①支持为同一个DOM元素注册多个同类型事件;②可将事件分为事件捕获和事件冒泡。
代码:addEventListener(event,function,useCapture布尔值)默认为false冒泡,true为捕获
attachEvent() //IE8及IE更早版本 detachEvent() //移除事件监听

//不使用事件捕获
window.onload = function(){
    	let oBox = document.getElementById("box");
    	oBox.onclick = function(){
    		alert(1);   //不触发
    }
    	oBox.onclick = function(){
    		alert(2);   //触发
    	}
}
//使用事件捕获
window.onload = function(){
    oBox.addEventListener("click",function(){
    	alert(1);   //触发
    })
	oBox.addEventListener("click",function(){
		alert(2);   //触发
	})
}

事件捕获:当一个事件触发后,从Window对象触发,不断经过下级节点,直到目标节点。在事件到达目标节点之前的过程就是捕获阶段。所有经过的节点,都会触发对应的事件。

当为事件捕获(useCapture:true)时,先执行body的事件,再执行div的事件

事件冒泡:当事件到达目标节点后,会沿着捕获阶段的路线原路返回。同样,所有经过的节点,都会触发对应的事件。

当为事件冒泡(useCapture:false)时,先执行div的事件,再执行body的事件

先冒泡后捕获:根据w3c标准,应先捕获再冒泡。若要实现先冒泡后捕获,给一个元素绑定两个addEventListener,其中一个第三个参数设置为false(即冒泡),另一个第三个参数设置为true(即捕获),调整它们的代码顺序,将设置为false的监听事件放在设置为true的监听事件前面即可。

27、window的onload事件和domcontentloaded

window.onload:当一个资源及其依赖资源已完成加载时,将触发onload事件。
document.onDOMContentLoaded:当初始的HTML文档被完全加载和解析完成之后,DOMContentLoaded事件被触发,而无需等待样式表、图像和子框架的完成加载。
区别:
     ①onload事件是DOM事件,onDOMContentLoaded是HTML5事件。
     ②onload事件会被样式表、图像和子框架阻塞,而onDOMContentLoaded不会。
     ③当加载的脚本内容并不包含立即执行DOM操作时,使用onDOMContentLoaded事件是个更好的选择,会比onload事件执行时间更早。

28、for···in和for···of的区别:(for···in取key,for··of取value)

①从遍历数组角度来说,for···in遍历出来的是key(即下标),for···of遍历出来的是value(即数组的值);

var arr = [99,88,66,77];
for(let i in arr){
    console.log(i);   //0,1,2,3
}
for(let i of arr){
    consoel.log(i);   //99,88,66,77
}

②从遍历字符串的角度来说,同数组一样。
③从遍历对象的角度来说,for···in会遍历出来的为对象的key,但for···of会直接报错。

var obj = {name:"Bob",age:25};
for(var i in obj){
	console.log(i)  // name age
}
for(var i of obj){
	console.log(i)   //报错
}

29、函数柯里化(卡瑞化、加里化)?

概念:把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。容易理解的概念:Currying概念其实很简单,只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数(主要是利用闭包实现的)。
特点: 
   ①接收单一参数,将更多的参数通过回调函数来搞定;
   ②返回一个新函数,用于处理所有的想要传入的参数;
   ③需要利用call/apply与arguments对象收集参数;
   ④返回的这个函数正是用来处理收集起来的参数。
作用:能进行部分传值,而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参,这个时候柯里化和偏应用就能派上用场。
用途:我认为函数柯里化是对闭包的一种应用形式,延迟计算、参数复用、动态生成函数(都是闭包的用途)。

function add(x,y){  //普通函数
    console.log(x+y);
}
function curryingAdd(x){  //柯里化函数(闭包)
    return function(y){
        console.log(x+y);
    }
}
add(1,2)  //3
curryingAdd(1)(2)   //3  

30、JS预解析?

JS代码在执行前,浏览器会对js代码进行扫描,默认的把所有带var和function声明的变量进行提前的声明或者定义,遵循先解析后使用的原则。

31、call、applyd的区别,原生JS实现bind。(call,apply,bind三者用法和区别:角度可为参数、绑定规则,运行效率、运行情况)。

定义:
apply():调用一个对象的一个方法,用另一个对象替换当前对象,例如:B.apply(A,arguments);即A对象应用B对象的方法。
call():调用一个对象的一个方法,用另一个对象替换当前对象,例如:B.call(A,args1,args2,···);即A对象调用用B对象的方法。

作用:①改变this指向;②借用别的对象的方法;③单纯的调用函数;④实现继承;

function add(a,b){
	return a+b;
}
function sub(a,b){
	return a-b;
}
var a1 = add.apply(sub,[4,2]); //sub调用add的方法
var a2 = sub.apply(add,[4,2]); //add调用sub的方法
var a3 = add.call(sub,4,2); //sub调用add的方法
console.log(a1);  //6
console.log(a2);  //2
console.log(a3);  //6

//改变this指向
var obj = {
	name:"Bob"
}
var name = "Tom";
function test(){
	console.log(this.name);
	console.log(this);
}
test();  // Tom   Window
test.call(obj);  //Bob {name:"Bob"}

//借用别的对象的方法
var Person1 = function(){
	this.name = "Bob";
}
var Person2 = function(){
	this.getName = function(){
		console.log(this.name);
	}
	Person1.call(this);//this指向Person2,结果相当于给Person2加了name属性
}
var person = new Person2();
person.getName();   //Bob

//单纯的函数调用:
function add(){
    alert(1);
}
add.call();

apply、call和bind的区别:
相似之处:
    ①都是用来改变函数的this对象的指向的;
    ②第一个参数都是this要指向的对象;
    ③都可以利用后续参数传参;
区别:
①apply、call、bind的第一个参数都是this要指向的对象,但apply只有两个参数,第二个参数为一个数组,需要传输的参数值须全部放到数组中。而call、bind一样,参数用逗号分开。
②apply、call返回的的是一个值,而bind返回的是一个函数,需要执行这个函数才会得到值。

32、立即执行函数和使用场景

立即执行函数:( function( ){ })( ) 返回值可以为基本数据类型,也能返会任何类型的值。
写法原因:因为在 javascript 里,括号内部不能包含语句,当解析器对代码进行解释的时候,先碰到了(),然后碰到function关键字就会自动将()里面的代码识别为函数表达式而不是函数声明。
作用:立即执行函数会形成一个单独的作用域,我们可以封装一些临时变量或者局部变量,避免污染全局变量。
使用场景:
①代码在页面加载完成之后,不得不执行一些设置工作,比如时间处理器,创建对象等等。
② 所有的这些工作只需要执行一次,比如只需要显示一个时间。
③需要一些临时的变量,但是初始化过程结束之后,就再也不会被用到,我们可以用立即执行函数——去将我们所有的代码包裹在它的局部作用域中,不会让任何变量泄露成全局变量。

33、iframe的优缺点有哪些?

优点:
①iframe能够原封不动的把嵌入的网页展现出来;
②如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
③网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。
④如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。
缺点:
①会产生很多页面不易管理;
②iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
③代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化。
④很多的移动设备(PDA 手机)无法完全显示框架,设备兼容性差。
⑤iframe框架页面会增加服务器的http请求,对于大型网站是不可取的。
本题延申:
frame框架:
优点:
①重载页面时不需要重载整个页面,只需要重载页面中的一个框架页(减少了数据的传输,加快了网页下载速度);
②技术易于掌握,使用方便,使用者众多,可主要应用于不需搜索引擎来搜索的页面;
③方便制作导航栏 ;
缺点:
①搜索引擎程序不能解读这种页面;
②不能打印全框架;
③浏览器的后退按钮无效;
④手机等终端设备无法显示全部框架内容;
iframe和frame区别:
①frame不能脱离frameSet单独使用,iframe可以;
②frame不能放在body中,否则不能正常显示,frame不能和body同时使用,iframe可以;
③嵌套在frameSet中的iframe必需放在body中,不嵌套在frameSet中的iframe可以随意使用;
④frame的高度只能通过frameSet控制;iframe可以自己控制,不能通过frameSet控制;
⑤iframe 可以放到表格里面。frame 则不行。 

34、查找数组重复项

       查找该元素首次出现的位置和最后出现的位置下标是否相同,同时判断新数组中是否不存在该元素,如果都满足则添加进新数组中去。

var arr = [1,2,45,44,45,2,89,1,1,2,1,2,44];
Array.prototype.unique = function(){
	var arr = this;
	var box = [];
	for(var str of arr){
		if(arr.indexOf(str) != arr.lastIndexOf(str) && box.indexOf(str) == -1){
			box.push(str);
		}
	}
	return box;
}
console.log(arr.unique());

35、数组扁平化

var arr = [1,2,[3,4,[5,6,[7,8,[9,10]]]]];

第一种:

function flatten(arr){
	var box = [];
	arr.map(v => {
		if(Array.isArray(v)){
			box = box.concat(flatten(v))
		}else{
			box.push(v);
		}
	})
	return box;
}
console.log(flatten(arr));

第二种(不推荐):

function flatten(arr){
	return arr.toString().split(",").map(v => {
		return Number(v);
	})
}
console.log(flatten(arr));

第三种:

function flatten(arr){
	console.log(arr.join(","))
	return arr.join(",").split(",").map(v => {
		return parseInt(v);
	})
}
console.log(flatten(arr));

第四种:

var arr = [1,2,[3,4,[5,6,[7,8,[9,10]]]]];
function flatten(arr){
	return arr.reduce((result,item) => {
		console.log(result,item)
		return result.concat(Array.isArray(item) ? flatten(item) : item);
	},[]);
}
console.log(flatten(arr)); 

第五种:

console.log([].concat(...arr));
function flatten(arr){
	while(arr.some(item => Array.isArray(item))){
		arr = [].concat(...arr);
	}
	return arr;
}
console.log(flatten(arr));    

36、BOM属性对象方法

BOM,即 JavaScript可以进行操作的浏览器的各个功能部件的接口。
⑴window对象:
①window的方法:confirm(确认框),open(url)打开新的窗口,close()关闭窗口;
②window的属性:closed,opener。
我们有的时候需要代开我们的子窗体,域名下面还有一个新的域名,也就是子域名,子网页 var myWindow = window.open("xxx.html"); 我们打开一个子网页, 会返回给我们一个值的,这个值代表另一个页面的window属性可以通过myWindow.closed来查看另一个页面的是否关闭。

⑵Navigator对象(导航器对象):
appCodeName:返回浏览器的代码名;
appName:返回浏览器名字;
appVersion:返回浏览器的平台和版本信息;
cookieEnabled:返回指明浏览器中是否启用cookie的布尔值;
platform:返回运行浏览器的操作系统平台;
userAgent:返回客户机发送服务器的user-agent头部的值;

⑶screen(显示器对象):
avaiHeight:返回显示屏幕的可用高度;
availWidth:返回显示屏幕的可用宽度;
height:返回屏幕的像素高度;
width:返回屏幕的像素宽度;
colorDepth:返回屏幕颜色的位数;

⑷history(历史对象):
back():返回前一个URL;
forward():返回下一个URL;
go():返回某个具体页面;

⑸localtion(位置对象):
hash:返回或设置从井号(#)开始的URL;
host:设置或返回主机名和当前URL的端口号;
href:设置或者返回完整的URL;
hostname:设置或返回当前URL主机名;
search:设置或者返回从问号(?)开始的URL;
port:设置或返回当前URL的端口号;

37、服务端渲染

定义:将组件或页面通过服务器生成html字符串,在发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。
解释:服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。有了服务端渲染,当请求用户页面时,返回的body里已经有了首屏的html结构,之后结合css显示出来。

优点:
①首屏渲染快(关键性问题):相比于加载单页应用,我只需要加载当前页面的内容,而不需要像 React 或者 Vue 一样加载全部的 js 文件;
②SEO(搜索引擎)优化:不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本(Google除外,据说Googlebot可以运行javaScript)。使用了React或者其它MVVM框架之后,页面大多数DOM元素都是在客户端根据js动态生成,可供爬虫抓取分析的内容大大减少。另外,浏览器爬虫不会等待我们的数据完成之后再去抓取我们的页面数据。服务端渲染返回给客户端的是已经获取了异步数据并执行JavaScript脚本的最终HTML,网络爬中就可以抓取到完整页面的信息。
③可以生成缓存片段、节能;

缺点:用户体验较差,不容易维护、通常前端改了部分html或者css,后端也需要改;
使用场景:vue全家桶或者react全家桶,都是推荐通过服务端渲染来实现路由的。

38、垃圾回收机制

什么是垃圾:一般来说没有被引用的对象就是垃圾,就是要被清除, 有个例外如果几个对象引用形成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,也要被清除。
方法:
①JS具有垃圾自动回收的机制:周期性执行,找出那些不在继续使用的变量,然后释放其内存。
②标记清除(常见):当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。
③引用计数:
     原理:跟踪记录每个值被引用的次数。
     工作流程:当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。

39、eventloop(事件循环):进程和线程,任务队列;

浏览器内核是多线程,JavaScript是单线程;
JS单线程详解:因为 js 是面向客户端的一门语言,主要是用户交互,操作dom,渲染数据。试想一下,如果是多线程,我们在一个线程删除了一个dom节点,另外一个线程添加了一个dom节点,以那个线程为主呢,就会出现混乱的情况。当然你可以说我们在操作一个dom之后加上锁,只允许一个线程操作,这样其实增加了程序的复杂度,并不是一个好办法。

单线程产生的问题:必须要等待前一个程序执行完毕才执行下一个,所以将程序分为了两类:同步任务和异步任务。异步任务又可以分为宏任务和微任务。

栈:先进后出的数据结构,存储基本数据类型的变量。
堆:主要负责引用数据类型的存储。

任务队列:为什么会有任务队列呢,还是因为 javascript 单线程的原因,单线程,就意味着一个任务一个任务的执行,执行完当前任务,执行下一个任务,这样也会遇到一个问题,就比如说,要向服务端通信,加载大量数据,如果是同步执行,js 主线程就得等着这个通信完成,然后才能渲染数据,为了高效率的利用cpu, 就有了同步任务和异步任务之分。

同步任务: 指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
异步任务: 指的是不进入主线程,某个异步任务可以执行了,该任务才会进入主线程执行。


宏任务macrotask: 可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。常见的宏任务:script, setTimeout, setInterval, setImmediate, I/O, UI rendering。
微任务microtask(异步): 可以理解是在当前task执行结束后立即执行的任务。常见的微任务:process.nextTick(Nodejs),Promise.then(), MutationObserver。

40、如何快速让字符串变成已千为精度的数字

var str = "10000000000";

第一种:把数字转换成字符串后,打散为数组,再从末尾开始,逐个把数组中的元素插入到新数组(result)的开头。 每插入一个元素,counter就计一次数(加1),当counter为3的倍数时,就插入一个逗号,但是要注意开头(i为0时)不需要逗号。最后通过调用新数组的join方法得出结果。

String.prototype.toThousands = function(){
	var num = this;
    var result = [ ], counter = 0;
    num = (num || 0).toString().split('');
    for (var i = num.length - 1; i >= 0; i--) {
        counter++;
        result.unshift(num[i]);
        if (!(counter % 3) && i != 0) { result.unshift(','); }
    }
    return result.join('');
}
console.log(str.toThousands());

第二种:通过正则表达式循环匹配末尾的三个数字,每匹配一次,就把逗号和匹配到的内容插入到结果字符串的开头,  然后把匹配目标(num)赋值为还没匹配的内(RegExp.leftContext)。         如果数字的位数是3的倍数时,最后一次匹配到的内容肯定是三个数字,但是最前面的三个数字前不需要加逗号;如果数字的位数不是3的倍数,那num变量最后肯定会剩下1到2个数字,循环过后,要把剩余的数字插入到结果字符串的开头。

function toThousands(num) {
    var num = (num || 0).toString(), re = /\d{3}$/, result = '';
    while ( re.test(num) ) {
        result = RegExp.lastMatch + result;
        if (num !== RegExp.lastMatch) {
            result = ',' + result;
            num = RegExp.leftContext;
        } else {
            num = '';
            break;
        }
    }
    if (num) { result = num + result; }
    return result;
}
console.log(toThousands(str));  

第三种:第二种的改良版

function toThousands(num) {
    var num = (num || 0).toString(), result = '';
    while (num.length > 3) {
        result = ',' + num.slice(-3) + result;
        num = num.slice(0, num.length - 3);
    }
    if (num) { result = num + result; }
    return result;
}
console.log(toThousands(str));  

第四种:懒人版

function toThousands(num) {
    return (num || 0).toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,');
}
console.log(toThousands(str));

41、Promise的解释:

          Promise是异步b编程解决方案之一。最大的好处是提供了一个then,来为异步提供回调函数。其先进之处是可以在then方法中继续写Promise对象并f返回,然后继续用then来进行回调操作,并且能够在外层捕获异步函数的异常信息。
⑴Promise用法:

const fn = new Promise(function(resolve,reject){
    axios.get(url).then(res => {
        resolve(res);
    }).catch(err => {
        reject(err);
    })
}).then((res) => {
    console.log(res);
}).catch((err) => {
    console.log(err);
})

⑵Promise原理:
      在Promise的内部,有一个状态管理器的存在,有三种状态:pending、fulfilled、rejected。
      ①promised对象初始化为pending;
      ②当调用resolve(成功),会由pending => fulfilled。
      ③dda当调用reject(失败),会由pending => rejected。
        看上面的的代码中的resolve(num)其实是将promise的状态由pending改为fulfilled,然后向then的成功回掉函数传值,reject反之。但是需要记住的是注意promsie状态 只能由 pending => fulfilled/rejected, 一旦修改就不能再变。promise.then方法每次调用,都返回一个新的promise对象 所以可以链式写法(无论resolve还是reject都是这样)

⑶Promise的三个状态:
    pending:异步任务正在进行中;
    resolved(也可以叫fulfilled),异步任务执行成功;
    rejected,异步任务执行失败。

⑷Promise对象初始化:
     ① new Promise(fn);
     ②Promise.resolve(fn);

⑸Promise特点:
    ①对象的状态不受外界影响;
    ②一旦状态改变,就不会再变,任何时候都可以得到这个结果;

⑹Promise方法:
       ①Promise.all( [promise1,promise2,promise3] ).then( );
作为参数的几个promise对象一旦有一个的状态为rejected,则all的返回值就是rejected。
当这几个作为参数的函数的返回状态为fulfilled时,至于输出的时间就要看谁跑的慢了。

var   p1 = Promise.resolve(1),
      p2 = Promise.reject(2),
      p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then((res)=>{
    //then方法不会被执行
    console.log(results);
}).catch((err)=>{
    //catch方法将会被执行,输出结果为:2
    console.log(err);
});

        ②promise.race( ):从字面意思上理解就是竞速,那么理解起来上就简单多了,也就是说在数组中的元素实例那个率先改变状态,就向下传递谁的状态和异步结果。但是,其余的还是会继续进行的。

let p1 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('1s') //1s后输出
    resolve(1)
  },1000)
})
let p10 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('10s') //10s后输出
    resolve(10) //不传递
  },10000)
})
let p5 = new Promise((resolve)=>{
  setTimeout(()=>{
    console.log('5s') //5s后输出
    resolve(5) //不传递
  },5000)
})
Promise.race([p1, p10, p5]).then((res)=>{
    console.log(res); // 最后输出
})
//结果:
1s
1
5s
10s

42、模块化

模块定义:将一个复杂的程序依据一定的规则封装成几个块(文件),并进行组合在一起,块的内部数据是私有的,只是向外部暴露一些接口(方法)与外部其它模块通信。

模块的组成:数据(内部属性)、操作数据的行为(内部的函数);

模块化:编码时是按照模块一个一个编码的, 整个项目就是一个模块化的项目。能够帮助开发者拆分和组织代码,解决JS全局作用域的污染问题。

第一种:CommonJS(同步加载)主要用在Node开发上,每个文件j就是一个模块,每个文件都有自己的一个作用域。通过module.exports暴露public成员,通过let xm = require(模块路径)来引用。
第二种:AMD:AMD规范的被依赖模块是异步加载的,而定义的模块是被当作回调函数来执行的,依赖于require.js模块管理工具库。当然,AMD规范不是采用匿名函数自调用的方式来封装,我们依然可以利用闭包的原理来实现模块的私有成员和公有成员。

// 定义AMD规范的模块
define([function() {
  return 模块
})
//私有成员
define(['module1', 'module2'], function(m1, m2) {
  let x = 1;
  function add() {
    x += 1;
    return x;
  }
  return { add };
})

第三种:CMD:CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。AMD 推崇依赖前置,CMD 推崇依赖就近。CMD集成了CommonJS和AMD的的特点,支持同步和异步加载模块。CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。因此,在CMD中require函数同步加载模块时没有HTTP请求过程。

define(function(require, exports, module) {
  //  同步加载模块
  var a = require('./a');
  a.doSomething();
  // 异步加载一个模块,在加载完成时,执行回调
  require.async(['./b'], function(b) {
    b.doSomething();
  });
  // 对外暴露成员
  exports.doSomething = function() {};
});
// 使用模块
seajs.use('path');

第四种:module:ES6的模块化已经不是规范了,而是JS语言的特性。随着ES6的推出,AMD和CMD也随之成为了历史。ES6模块与模块化规范相比,有两大特点:

     模块化规范输出的是一个值的拷贝,ES6 模块输出的是值的引用。模块化规范是运行时加载,ES6 模块是编译时输出接口。
      模块化规范输出的是一个对象,该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,ES6 module 是一个多对象输出,多对象加载的模型。从原理上来说,模块化规范是匿名函数自调用的封装,而ES6 module则是用匿名函数自调用去调用输出的成员。

//react种运用的就是ES6的模块化
var module1 = value1;
var module2 = value2;
export {module1,module2}  //导出模块
import {module1,module2} from "模块路径/模块名" //引入模块

43、new的原理

new实际上是在堆内存中开辟一个空间。
①创建一个空对象,构造函数中的this指向这个空对象;
②这个新对象被执行[ [ 原型 ] ]连接;
③执行构造函数方法,属性和方法被添加到this引用的对象中;
④如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。

function _new(){
    let target = {};   //创建的新对象
    let [constructor,...args] = [...arguments];
       //执行[[原型]]连接,target是constructor的实例
    target.__proto__ = constructor.prototype;
        //执行构造函数,将属性或方法添加到创建的空对象上
    let result = constructor.prototype;
    if(result && (typeof (result) == "object" || typeof (result) == "function")){
           //如果构造函数执行的结构返回的是一个对象,那么返回这个对象
        return result;
    }
       //如果构造函数返回的不是一个对象,返回创建的对象
    return target;
}

自己理解的new:
         new实际上是在堆内存中开辟一个新的空间。首先创建一个空对象obj,然后呢,把这个空对象的原型(__proto__)和构造函数的原型对象(constructor.prototype)连接(说白了就是等于);然后执行函数中的代码,就是为这个新对象添加属性和方法。最后进行判断其返回值,如果构造函数返回的是一个对象,那就返回这个对象,如果不是,那就返回我们创建的对象。

44、数组和类数组

类数组:
①拥有length属性,其它属性(索引)为非负整数;
②不具有数组所具有的方法;
③类数组是一个普通对象,而真实的数组是Array类型。
常见的类数组:arguments,document.querySelectorAll得到的列表,jQuery对象($("div"));

45、call、apply、bind封装

call函数

// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.mycall = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('not funciton')
  }
  context = context || window
  context.fn = this
  let arg = [...arguments].slice(1)
  let result = context.fn(...arg)
  delete context.fn
  return result
}

apply函数

// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.myapply = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('not funciton')
  }
  context = context || window
  context.fn = this
  let result
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result
}

bind函数

// 思路:类似call,但返回的是函数
Function.prototype.mybind = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  let _this = this
  let arg = [...arguments].slice(1)
  return function F() {
    // 处理函数使用new的情况
    if (this instanceof F) {
      return new _this(...arg, ...arguments)
    } else {
      return _this.apply(context, arg.concat(...arguments))
    }
  }
}

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

          " == "操作符在左右数据不一致的时候,会先进行隐式转换,该值意味着不是基本数据类型,因为如果a是null或者undefined、bool类型都不可能返回true;可以推测a是复杂数据类型。

方法一:数组的 toString 接口默认调用数组的 join 方法,重新 join 方法

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

方法二:利用数据劫持(Proxy/Object.definedProperty)

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

47、var、let和const的区别

①变量提升:var存在变量提升,可以在声明之前使用,而let和const在声明之前使用会报错。

②重复声明:在相同作用域内,let和const不允许重复声明。

③暂时性死区:

var tmp = 123;if(true){    tmp = "abc";    let tmp;  //报错}

④初始值:const声明的是一个常量,不可以改变,一旦声明就必须给赋值。const所说的一旦声明值就不能改变,实际上指的是:变量指向的那个内存地址所保存的数据不得改动。