教务出题,全栈请求出战5678(marksheng)

70 篇文章 1 订阅
39 篇文章 1 订阅

第五周

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来生成实例对象。

 

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();}   

 

 

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

13. ES6解构赋值的用处

用处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")

 

 

14.数组去重——————————————————重点

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);

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

  1. ES7新增(不定时添加)
  • 求幂运算符(**)

var x = 5 ** 3;   //  5的三次方

console.log(x);   //输出: 125

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

16.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}

 

17.总结异步编程的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}

  1. 设计模式(下面只列举了常用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);

 

(2)get请求中文参数出现乱码解决方法有两个:

①修改tomcat配置文件添加编码与工程编码一致,如下:

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. 策略模式中的代码可以复用。

⑨ 发布-订阅者模式:发布---订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。

⑩ 中介者模式:中介者模式的作用是解除对象与对象之间的耦合关系,增加一个中介对象后,所有的相关对象都通过中介者对象来通信,而不是相互引用,所以当一个对象发送改变时,只需要通知中介者对象即可。中介者使各个对象之间耦合松散,而且可以独立地改变它们之间的交互。

19.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);

    }

}

 

20.图片的懒加载、预加载:

原理:

       预加载原理:就是在网页全部加载之前,提前加载图片,当用户需要查看时可直接从本地缓存中渲染,以提供给用户更好的体验,减少等待的时间。

       图片懒加载原理(缓载):通过监听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地址">

 

 

 



第六周====================================================

1.防抖、节流?

防抖:

所谓防抖,就是指触发事件后在 n秒内函数只能执行一次,如果在 n秒内又触发了事件,则会重新计算函数执行时间

 

//防抖

// 思路:在规定时间内未触发第二次,则执行

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))

 

 

节流:

所谓节流,就是指连续触发事件但是在 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))

 

 

2. 页面加载进度条的实现

①定时器加载(原理):

设置 固定的时间后 将 遮罩层 和 加载图片 隐藏,显示页面内容。

 

②通过加载状态事件实现进度条(原理):

document.onreadystatechange(页面加载状态改变时的事件),document.readyState(返回当前文档的状态);

 

③通过CSS3来实现进度条;

 

④实时获取加载数据的进度条(原理):通过加载图像来实现效果。

 

⑤根据加载进度来改变进度条的长度(width值)

 

⑥根据文件的加载顺序来实现加载进度条。

 

3. 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

 

 

4. 对象和面向对象

对象:

属性和方法的集合叫做对象(万物皆对象)。

 

面向对象:

首先就是找对象,找不到就添加,如果该对象不具备所需要的方法或属性,那就给它添加。面向对象是一种编程思维的改变。通过原型的方式来实现面向对象编程。

 

创建对象的方式(4种):

new Object、字面量、构造函数、原型。

 

 

5. 函数式编程

含义:

函数式编程是一种强调以函数为主的软件开发风格。通过组合纯函数,避免共享状态、可变作用和副作用来构建软件的过程。

 

目的:

使用函数来抽象作用在数据之上的控制流和操作,从而在系统中消除副作用并减少对状态的改变。

 

特点:

函数式编程是声明性的而不是命令式的,应用状态流经纯函数中。相比于面向对象编程,其中的应用状态经常是共享的,并且和方法一起定义在一些对象中。

 

 

6. 怎么判断两个对象是否相等?

① 首先比较两个对象的长度,如果长度不相等使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));

 

 

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

事件委托:

又叫事件代理,利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

 

原理:

事件冒泡机制,从最深的节点开始,然后逐步向上传播事件。

 

作用:

①支持为同一个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的监听事件前面即可。

 

 

 

8. window的onload事件和domcontentloaded

window.onload:

当一个资源及其依赖资源加载完时,将触发onload事件。

 

document.onDOMContentLoaded:

当初始的HTML文档被完全加载和解析完成之后,DOMContentLoaded事件被触发,而无需等待样式表、图像和子框架的完成加载。

 

区别:

①onload事件是DOM事件,onDOMContentLoaded是HTML5事件。

②onload事件会被样式表、图像和子框架阻塞,而onDOMContentLoaded不会。

③当加载的脚本内容并不包含立即执行DOM操作时,使用onDOMContentLoaded事件是个更好的选择,会比onload事件执行时间更早。

 

 

 

9. 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)   //报错

}

 

 

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

概念:

把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

 

容易理解的概念:

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  

 

 

 

11. JS预解析?

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

 

 

12. 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返回的是一个函数,需要执行这个函数才会得到值。

 

13. 立即执行函数和使用场景

立即执行函数:

( function( ){ })( ) 返回值可以为基本数据类型,也能返会任何类型的值。

 

写法原因:

因为在 javascript 里,括号内部不能包含语句,当解析器对代码进行解释的时候,先碰到了(),然后碰到function关键字就会自动将()里面的代码识别为函数表达式而不是函数声明。

 

作用:

立即执行函数会形成一个单独的作用域,我们可以封装一些临时变量或者局部变量,避免污染全局变量。

 

使用场景:

①代码在页面加载完成之后,不得不执行一些设置工作,比如时间处理器,创建对象等等。

② 所有的这些工作只需要执行一次,比如只需要显示一个时间。

③需要一些临时的变量,但是初始化过程结束之后,就再也不会被用到,我们可以用立即执行函数——去将我们所有的代码包裹在它的局部作用域中,不会让任何变量泄露成全局变量。

 

 

14. 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 则不行。 

 

 

15. 查找数组重复项

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

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());

 

 

16. 数组扁平化

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));    

 

 

17. 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的端口号;

 

 

18. 服务端渲染

定义:

将组件或页面通过服务器生成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全家桶,都是推荐通过服务端渲染来实现路由的。

 

 

19. 垃圾回收机制

什么是垃圾:

一般来说没有被引用的对象就是垃圾,就是要被清除, 有个例外如果几个对象引用形成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,也要被清除。

 

方法:

①JS具有垃圾自动回收的机制:

周期性执行,找出那些不在继续使用的变量,然后释放其内存。

 

②标记清除(常见):

当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。

 

③引用计数:

     原理:跟踪记录每个值被引用的次数。

     工作流程:当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。

 

 

20. 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。

 

 

 

第五七

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

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));

 

2.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

3.模块化

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

 

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

 

模块化:编码时是按照模块一个一个编码的, 整个项目就是一个模块化的项目。能够帮助开发者拆分和组织代码,解决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 "模块路径/模块名" //引入模块

4.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)连接(说白了就是等于);然后执行函数中的代码,就是为这个新对象添加属性和方法。最后进行判断其返回值,如果构造函数返回的是一个对象,那就返回这个对象,如果不是,那就返回我们创建的对象。

 

5.数组和类数组

类数组:

①拥有length属性,其它属性(索引)为非负整数;

②不具有数组所具有的方法;

③类数组是一个普通对象,而真实的数组是Array类型。

常见的类数组:arguments,document.querySelectorAll得到的列表,jQuery对象($("div"));

6.call、apply、bind封装

call函数

// 思路:将要改变this指向的方法挂到目标this上执行并返回

Function.prototype.mycall = function (context) {

  if (typeofthis !== '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 (typeofthis !== '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 (typeofthis !== '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))

    }

  }

}

7.如何让(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);

8.var、let和const的区别

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

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

③暂时性死区:

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

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

9.DOM 事件有哪些阶段?谈谈对事件代理的理解

分为三大阶段:捕获阶段--目标阶段--冒泡阶段

事件代理简单说就是:事件不直接绑定到某元素上,而是绑定到该元素的父元素上,进行触发事件操作时(例如'click'),再通过条件判断,执行事件触发后的语句(例如'alert(e.target.innerHTML)')

好处:(1)使代码更简洁;(2)节省内存开销

10.async 和 await

Generator 函数的语法糖。

async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await`就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

优点 : Async 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用 Generator 写法,自动执行器需要用户自己提供。

11.transform、translate、transition 分别是什么属性?CSS 中常用的实现动画方式

三者属性说明
transform 是指变换、变形,是 css3 的一个属性,和 width,height 属性一样;
translate 是 transform 的属性值,是指元素进行 2D(3D)维度上位移或范围变换;
transition 是指过渡效果,往往理解成简单的动画,需要有触发条件。

这里可以补充下 transition 和 animation 的比较,前者一般定义开始结束两个状态,需要有触发条件;而后者引入了关键帧、速度曲线、播放次数等概念,更符合动画的定义,且无需触发条件

12.对前端性能优化有什么了解?一般都通过那几个方面去优化的?

1.减少请求数量

2.减小资源大小

3.优化网络连接

4.优化资源加载

5.减少重绘回流

6.性能更好的API

7.webpack优化

13.实现一个节流函数

 // 思路:在规定时间内只触发一次

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))

14.手写实现 AJAX

// 1. 简单流程

// 实例化

let xhr = new XMLHttpRequest()

// 初始化

xhr.open(method, url, async)

// 发送请求

xhr.send(data)

// 设置状态变化回调处理请求结果

xhr.onreadystatechange = () => {

  if (xhr.readyStatus === 4 &&xhr.status === 200) {

    console.log(xhr.responseText)

  }

}

// 2. 基于promise实现

function ajax (options) {

  // 请求地址

  const url = options.url

  // 请求方法

  const method = options.method.toLocaleLowerCase() || 'get'

  // 默认为异步true

  const async = options.async

  // 请求参数

  const data = options.data

  // 实例化

  const xhr = new XMLHttpRequest()

  // 请求超时

  if (options.timeout&&options.timeout> 0) {

xhr.timeout = options.timeout

  }

  // 返回一个Promise实例

  return new Promise ((resolve, reject) => {

xhr.ontimeout = () => reject && reject('请求超时')

    // 监听状态变化回调

xhr.onreadystatechange = () => {

      if (xhr.readyState == 4) {

        // 200-300 之间表示请求成功,304资源未变,取缓存

        if (xhr.status>= 200 &&xhr.status< 300 || xhr.status == 304) {

          resolve && resolve(xhr.responseText)

        } else {

          reject &&reject()

        }

      }

    }

    // 错误回调

xhr.onerror = err => reject && reject(err)

    let paramArr = []

    let encodeData

    // 处理请求参数

    if (data instanceof Object) {

      for (let key in data) {

        // 参数拼接需要通过 encodeURIComponent 进行编码

paramArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))

      }

encodeData = paramArr.join('&')

    }

    // get请求拼接参数

    if (method === 'get') {

      // 检测url中是否已存在 ? 及其位置

      const index = url.indexOf('?')

      if (index === -1) url += '?'

      else if (index !== url.length -1) url += '&'

      // 拼接url

url += encodeData

    }

    // 初始化

xhr.open(method, url, async)

    // 发送请求

    if (method === 'get') xhr.send(null)

    else {

      // post 方式需要设置请求头

xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8')

xhr.send(encodeData)

    }

  })

}

15rem 基本设置?

// 提前执行,初始化 resize 事件不会执行

setRem()

// 原始配置

function setRem () {

  let doc = document.documentElement

  let width = doc.getBoundingClientRect().width

  let rem = width / 75

doc.style.fontSize = rem + 'px'

}

// 监听窗口变化

addEventListener("resize", setRem)

16.实现懒加载

 

<ul>

<li><imgsrc="./imgs/default.png" data="./imgs/1.png" alt=""></li>

<li><imgsrc="./imgs/default.png" data="./imgs/2.png" alt=""></li>

<li><imgsrc="./imgs/default.png" data="./imgs/3.png" alt=""></li>

<li><imgsrc="./imgs/default.png" data="./imgs/4.png" alt=""></li>

<li><imgsrc="./imgs/default.png" data="./imgs/5.png" alt=""></li>

<li><imgsrc="./imgs/default.png" data="./imgs/6.png" alt=""></li>

<li><imgsrc="./imgs/default.png" data="./imgs/7.png" alt=""></li>

<li><imgsrc="./imgs/default.png" data="./imgs/8.png" alt=""></li>

<li><imgsrc="./imgs/default.png" data="./imgs/9.png" alt=""></li>

<li><imgsrc="./imgs/default.png" data="./imgs/10.png" alt=""></li>

</ul>

复制let imgs =  document.querySelectorAll('img')

// 可视区高度

let clientHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight

function lazyLoad () {

  // 滚动卷去的高度

  let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop

  for (let i = 0; i<imgs.length; i ++) {

    // 图片在可视区冒出的高度

    let x = clientHeight + scrollTop - imgs[i].offsetTop

    // 图片在可视区内

    if (x > 0 && x <clientHeight+imgs[i].height) {

imgs[i].src = imgs[i].getAttribute('data')

    }

  }

}

// addEventListener('scroll', lazyLoad) or setInterval(lazyLoad, 1000)

17.实现一个简单路由

// hash路由

class Route{

constructor(){

    // 路由存储对象

this.routes = {}

    // 当前hash

this.currentHash = ''

    // 绑定this,避免监听时this指向改变

this.freshRoute = this.freshRoute.bind(this)

    // 监听

window.addEventListener('load', this.freshRoute, false)

window.addEventListener('hashchange', this.freshRoute, false)

  }

  // 存储

storeRoute (path, cb) {

this.routes[path] = cb || function () {}

  }

  // 更新

freshRoute () {

this.currentHash = location.hash.slice(1) || '/'

this.routes[this.currentHash]()

  }

}。

18.实现一个双向数据绑定

let obj = {}

let input = document.getElementById('input')

let span = document.getElementById('span')

// 数据劫持

Object.defineProperty(obj, 'text', {

  configurable: true,

  enumerable: true,

get() {

    console.log('获取数据了')

  },

  set(newVal) {

    console.log('数据更新了')

input.value = newVal

span.innerHTML = newVal

  }

})

// 输入监听

input.addEventListener('keyup', function(e) {

obj.text = e.target.value

})

19.实现一个基本的 Event Bus

// 组件通信,一个触发与监听的过程

class EventEmitter {

  constructor () {

    // 存储事件

this.events = this.events || new Map()

  }

  // 监听事件

addListener (type, fn) {

    if (!this.events.get(type)) {

this.events.set(type, fn)

    }

  }

  // 触发事件

  emit (type) {

    let handle = this.events.get(type)

handle.apply(this, [...arguments].slice(1))

  }

}

 

// 测试

let emitter = new EventEmitter()

// 监听事件

emitter.addListener('ages', age => {

  console.log(age)

})

// 触发事件

emitter.emit('ages', 18)  // 18。

20.实现一个防抖函数

// 思路:在规定时间内未触发第二次,则执行

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))

第八周

  1. 浏览器的储存方式有哪些?

常见的浏览器存储主要有:
1.属于文档对象模型:documentcookie,
2.属于浏览器对象模型localStorage,sessionStorage,indexDB

Cookie

h5之前,存储主要用cookies,缺点是在请求头上带着数据,导致流量增加。大小限制4k

localStorage

以键值对(Key-Value)的方式存储,永久存储,永不失效,除非手动删除。IE8+支持,每个域名限制5M

sessionStorage

sessionStorage操作的方法与localStroage是一样的,区别在于sessionStorage在关闭页面后即被清空,而localStorage则会一直保存。很多时候数据只需要在用户浏览一组页面期间使用,关闭窗口后数据就可以丢弃了,这种情况使用sessionStorage就比较方便。

注意,刷新页面sessionStorage不会清除,但是打开同域新页面访问不到

区别:

  1. cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下。
  2. 存储大小限制不同,cookie数据不能超过4k,同时因为每次http请求都会携带cookie,所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
  3. 数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。
  4. 作用域不同,sessionStorage不在不同的浏览器页面中共享,即使是同一个页面;localStorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。
  5. Web Storage 支持事件通知机制,可以将数据更新的通知发送给监听者。
  6. Web Storage 的api接口使用更方便,cookie的原生接口不友好,需要自己封装。

 

2.对前后端跨域可以说一下吗?如何解决跨域的?

跨域

1、Jsonp,只能使用get提交,传输数据量有限

2、CORS(Corss-origin resource sharing)跨域资源共享,支持post提交

CORS原理,只需要在响应头header中注入Access-Control-Allow-Origin,这样浏览器坚持到,就可以跨域

 

一、CORS跨域需要浏览器与服务端同时支持,目前所有浏览器都支持,IE不能低于IE10

 

整个CORS通信,浏览器自动完成,对于开发者来说,CORS通信和AJAX通信没有区别,浏览器一旦发现AJAX请求跨域,

会自动添加一些附加的头信息

 

因此,实现CORS的关键是服务器,只要服务器实现CORS接口,就可以跨域通信

 

 

二、两种请求

浏览器将CORS请求分为两类,简单请求,非简单请求

 

只要同时满足以下两大条件,就属于简单请求。

 

1) 请求方法是以下三种方法之一:

HEAD

GET

POST

2)HTTP的头信息不超出以下几种字段:

Accept

Accept-Language

Content-Language

Last-Event-ID

Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

 

凡是不同时满足上面两个条件,就属于非简单请求。

浏览器对这两种请求的处理,是不一样的。

 

1、简单请求

浏览器会自动在头信息中添加一个Origin字段

origin字段用来说明,本次请求来至哪个服务器(协议+域名+端口)

 

如果指定的源,不在许可范围之内,会返回一个正常的http回应,浏览器发现这个响应头信息中不包含

Access-Control-Allow-Origin字段,就会抛出异常被onerror回调函数捕获,无法通过状态码判断,因为可能是200

 

如果指定的域名在许可的范围内,服务返回的响应的头信息就会多几个字段

Access-Control-Allow-Origin: http://api.bob.com

Access-Control-Allow-Credentials: true

Access-Control-Expose-Headers: FooBar

Content-Type: text/html; charset=utf-8

 

Access-Control-Allow-Origin:

该字段必须,它的值要么是请求时Origin自动的值,要么是*,代表接收所有域名的请求

 

Access-Control-Allow-Credentials:

可选,boolean值,代表是否允许发送cookie,这个值只能为true,不发送就删除该字段

在AJAX请求中打开withCredentials属性,否则浏览器可能不会发送,如果要发送,请求域名就不能为*

 

Access-Control-Expose-Headers:

XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:

Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。

如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。

上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

 

2、非简单请求

非简单请求:是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,

或者Content-Type字段的类型是application/json。

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。

只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

 

预检请求

"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,

表示请求来自哪个源。

 

除了Origin字段,"预检"请求的头信息包括两个特殊字段。

1)Access-Control-Request-Method

该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。

2)Access-Control-Request-Headers

该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。

 

如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。

这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。

 

XMLHttpRequest cannot load http://api.alice.com.

Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

 

三、spring 只需添加CorssOrgine注解实现跨域

corssOrgine注解,默认允许所有的域请求

3.浏览器 cookie 和 session 的认识。

 

session 是基于 cookie 实现的。cookie 保存在客户端浏览器中,而 session 保存在服务器上。cookie 机制是通过检查客户身上的“通行证”来确定客户身份的话,那么 session 机制就是通过检查服务器上的“客户明细表”来确认客户身份。session 相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

cookie 和 session 的区别:

  1. 存在的位置:
    cookie 存在于客户端,临时文件夹中;session 存在于服务器的内存中,一个 session 域对象为一个用户浏览器服务
  2. 安全性
    cookie 是以明文的方式存放在客户端的,安全性低,可以通过一个加密算法进行加密后存放;session 存放于服务器的内存中,所以安全性好
  3. 生命周期(以 20 分钟为例)
    cookie 的生命周期是累计的,从创建时,就开始计时,20 分钟后 cookie 生命周期结束;
    session 的生命周期是间隔的,从创建时,开始计时如在 20 分钟,没有访问 session,那么 session 生命周期被销毁。但是,如果在 20 分钟内(如在第 19 分钟时)访问过 session,那么,将重新计算 session 的生命周期。关机会造成 session 生命周期的结束,但是对 cookie 没有影响
  4. 访问范围
    cookie 为多个用户浏览器共享;session 为一个用户浏览器独享

4输入URL发生什么?

  1. DNS 域名解析(域名解析成ip地址,走UTP协议,因此不会有握手过程):浏览器将 URL 解析出相对应的服务器的 IP 地址(1. 本地浏览器的 DNS 缓存中查找 2. 再向系统DNS缓存发送查询请求 3. 再向路由器DNS缓存 4. 网络运营商DNS缓存 5. 递归搜索),并从url中解析出端口号
  2. 浏览器与目标服务器建立一条 TCP 连接(三次握手)
  3. 浏览器向服务器发送一条 HTTP 请求报文
  4. 服务器返回给浏览器一条 HTTP 响应报文
  5. 浏览器进行渲染
  6. 关闭 TCP 连接(四次挥手

 

5浏览器渲染的步骤

  1. HTML 解析出 DOM Tree
  2. CSS 解析出 Style Rules
  3. 两者关联生成 Render Tree
  4. Layout(布局)根据 Render Tree 计算每个节点的信息
  5. Painting 根据计算好的信息进行渲染整个页面

浏览器解析文档的过程中,如果遇到 script 标签,会立即解析脚本,停止解析文档(因为 JS 可能会改变 DOM 和 CSS,如果继续解析会造成浪费)。
如果是外部 script, 会等待脚本下载完成之后在继续解析文档。现在 script 标签增加了 defer 和 async 属性,脚本解析会将脚本中改变 DOM 和css的地方>解析出来,追加到 DOM Tree 和 Style Rules 上

 

6.页面渲染优化

基于对渲染过程的了解,推荐一下优化:

  1. HTML 文档结构层次尽量少,最好不深于 6 层
  2. 脚本尽量放后边,避免组织页面加载
  3. 少量首屏样式可以放在便签内
  4. 样式结构层次尽量简单
  5. 脚本减少 DOM 操作,减少回流,尽量缓存访问 DOM 的样式信息
  6. 尽量减少 JS 修改样式,可以通过修改 class 名的方式解决
  7. 减少 DOM 查找,缓存 DOM 查找结果
  8. 动画在屏幕外或页面滚动时,尽量停止

7强制缓存和协商缓存

  • 强制缓存是我们在第一次请求资源时在 http 响应头设置一个过期时间,在时效内都将直接从浏览器进行获取,常见的 http 响应头字段如 Cache-Control 和 Expires
  • 协商缓存是我们通过 http 响应头字段etag或者 Last-Modified 等判断服务器上资源是否修改,如果修改则从服务器重新获取,如果未修改则 304 指向浏览器缓存中进行获取

 

8  GET 和 POST 请求的区别

GET 参数通过url传递,POST 放在 body 中。(http 协议规定,url在请求头中,所以大小限制很小)

GET 请求在url中传递的参数是有长度限制的,而 POST 没有。原因见上↑↑↑

GET 在浏览器回退时是无害的,而 POST 会再次提交请求

GET 请求会被浏览器主动 cache,而 POST 不会,除非手动设置

GET 比 POST 更不安全,因为参数直接暴露在url中,所以不能用来传递敏感信息

对参数的数据类型,GET 只接受 ASCII字符,而 POST 没有限制

GET 请求只能进行url(x-www-form-urlencoded)编码,而 POST 支持多种编码方式

GET 产生一个 TCP 数据包;POST 产生两个 TCP 数据包。对于 GET 方式的请求,浏览器会把 http 的 header 和 data 一并发送出去,服务器响应200(返回数据)。而对于 POST,浏览器先发送 header,服务器响应100 continue,浏览器再发送 data,服务器响应200 ok(返回数据)

 

  1. HTTP1.0 / 1.1 / 2.0 及HTTPS?

HTTP1.1 是当前使用最为广泛的HTTP协议

HTTP1.0 和 HTTP1.1 相比

HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD 方法。HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。

1.缓存处理:在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。

2.带宽优化及网络连接的使用:HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。

3.错误通知的管理:在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。

4.Host头处理:在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。

5.长连接:HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。通过设置http的请求头部和应答头部,保证本次数据请求结束之后,下一次请求仍可以重用这一通道,避免重新握手。

 

HTTP2.0 和 HTTP1.X 相比

1.新的二进制格式(Binary Format):HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。

2.多路复用(MultiPlexing):即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。

3.header压缩:如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用了专门为首部压缩而设计的 HPACK 算法,使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。

4.服务端推送(server push):服务端推送能把客户端所需要的资源伴随着index.html一起发送到客户端,省去了客户端重复请求的步骤。正因为没有发起请求,建立连接等操作,所以静态资源通过服务端推送的方式可以极大地提升速度。例如我的网页有一个sytle.css的请求,在客户端收到sytle.css数据的同时,服务端会将sytle.js的文件推送给客户端,当客户端再次尝试获取sytle.js时就可以直接从缓存中获取到,不用再发请求了。

HTTPS 与 HTTP 相比

1.HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费。

2.HTTP协议运行在TCP之上,所有传输的内容都是明文,HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的。

3.HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4.HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题。

HTTPS 介绍:HTTPS在传输数据之前需要客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息。TLS/SSL协议不仅仅是一套加密传输的协议,TLS/SSL中使用了非对称加密,对称加密以及HASH算法。

 

10介绍下304过程?

a. 浏览器请求资源时首先命中资源的Expires 和 Cache-Control,Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效,可以通过Cache-control: max-age指定最大生命周期,状态仍然返回200,但不会请求数据,在浏览器中能明显看到from cache字样。

b. 强缓存失效,进入协商缓存阶段,首先验证ETagETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化。服务器根据客户端上送的If-None-Match值来判断是否命中缓存。

c. 协商缓存Last-Modify/If-Modify-Since阶段,客户端第一次请求资源时,服务服返回的header中会加上Last-Modify,Last-modify是一个时间标识该资源的最后修改时间。再次请求该资源时,request的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存。

 

11.HTTP 状态码

  • 1xx(临时响应)表示临时响应并需要请求者继续执行操作的状态码
    • 100 - 继续请求者应当继续提出请求。服务器返回此代码表示已收到请求的第一部分,正在等待其余部分
    • 101 - 切换协议请求者已要求服务器切换协议,服务器已确认并准备切换
  • 2xx(成功)表示成功处理了请求的状态码
    • 200 - 成功服务器已经成功处理了请求。通常,这表示服务器提供了请求的网页
    • 201 - 已创建请求成功并且服务器创建了新的资源
    • 202 - 已接受服务器已接受请求,但尚未处理
    • 203 - 非授权信息服务器已经成功处理了请求,但返回的信息可能来自另一来源
    • 204 - 无内容服务器成功处理了请求,但没有返回任何内容
    • 205 - 重置内容服务器成功处理了请求,但没有返回任何内容
    • 206 - 部分内容服务器成功处理了部分GET请求
  • 3xx(重定向)表示要完成请求,需要进一步操作;通常,这些状态代码用来重定向
    • 300 - 多种选择针对请求,服务器可执行多种操作。服务器可根据请求者(user agent)选择一项操作,或提供操作列表供请求者选择
    • 301 - 永久移动请求的网页已永久移动到新位置。服务器返回此响应(对GET或HEAD请求的响应)时,会自动将请求者转到新位置
    • 302 - 临时移动服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求
    • 303 - 查看其它位置请求者应当对不同的位置使用单独的GET请求来检索响应时,服务器返回此代码
    • 304 - 未修改自上次请求后,请求的网页未修改过。服务器返回此响应,不会返回网页的内容
    • 305 - 使用代理请求者只能使用代理访问请求的网页。如果服务器返回此响应,还表示请求者应使用代理
    • 307 - 临时性重定向服务器目前从不同位置的网页响应请求,但请求者应继续使用原有的位置来进行以后的请求
  • 4xx(请求错误)这些状态码表示请求可能出错,妨碍了服务器的处理
    • 400 - 错误请求服务器不理解请求的语法
    • 401 - 未授权请求要求身份验证。对于需要登录的网页,服务器可能返回此响应
    • 403 - 禁止服务器拒绝请求
    • 404 - 未找到服务器找不到请求的网页
    • 405 - 方法禁用禁用请求中指定的方法
    • 406 - 不接受无法使用请求的内容特性响应请求的网页
    • 407 - 需要代理授权此状态码与401(未授权)类似,但指定请求者应当授权使用代理
    • 408 - 请求超时服务器等候请求时发生超时
    • 409 - 冲突服务器在完成请求时发生冲突。服务器必须在响应中包含有关冲突的信息
    • 410 - 已删除如果请求的资源已永久删除,服务器就会返回此响应
    • 411 - 需要有效长度服务器不接受不含有效内容长度标头字段的请求
    • 412 - 未满足前提条件服务器未满足请求者在请求者设置的其中一个前提条件
    • 413 - 请求实体过大服务器无法处理请求,因为请求实体过大,超出了服务器的处理能力
    • 414 - 请求的URI过长请求的URI(通常为网址)过长,服务器无法处理
    • 415 - 不支持媒体类型请求的格式不受请求页面的支持
    • 416 - 请求范围不符合要求如果页面无法提供请求的范围,则服务器会返回此状态码
    • 417 - 未满足期望值服务器未满足“期望”请求标头字段的要求
  • 5xx(服务器错误)这些状态码表示服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错
    • 500 - 服务器内部错误服务器遇到错误,无法完成请求
    • 501 - 尚未实施服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码
    • 502 - 错误网关服务器作为网关或代理,从上游服务器无法收到无效响应
    • 503 - 服务器不可用服务器目前无法使用(由于超载或者停机维护)。通常,这只是暂时状态
    • 504 - 网关超时服务器作为网关代理,但是没有及时从上游服务器收到请求
    • 505 - HTTP版本不受支持服务器不支持请求中所用的HTTP协议版本

12  Web性能优化

一、尽量减少前端HTTP请求
浏览器并发线程数有限,所以针对资源文件的优化,一般有:

1、 合并脚本文件和CSS文件
2、 CSS Sprites利用CSS background相关元素进行背景图绝对定位,把多个图片合成一个图片。

二、浏览器缓存

在用户浏览网站的不同页面时,很多内容是重复的,比如相同的JS、CSS、图片等。如果我们能够建议甚至强制浏览器在本地缓存这些文件,将大大降低页面产生的流量,从而降低页面载入时间。
1、添加Expires头和Cache-Control
Expires头,浏览器端根据过期时间选择是否加载最新的版本。缺点是:需要服务器和客户端时间的严格同步,
HTTP1.1引入了Cache-Control头来克服Expires头的限制。Cache-Control使用max-age制定组件被缓存多久,使用秒为单位,例如Cache-Control:max-age=3600;表示组件将被缓存60分钟。如果max-age和Expires同时出现,则max-age有更高的优先级,浏览器会根据max-age的时间来确认缓存过期时间。
2、Last-Modified
在上次传输中,服务器给浏览器发送了Last-Modified或Etag数据,再次浏览时浏览器将提交这些数据到服务器,验证本地版本是否最新的,如果为最新的则服务器返回304代码,告诉浏览器直接使用本地版本,否则下载新版本。一般来说,有且只有静态文件,服务器端才会给出这些数据。

三、页面压缩

  • 1、GZIP
    • IE和Firefox浏览器都支持GZIP解码。后端服务器容器对数据GZIP压缩之后在传输到客户端,浏览器拿到数据后根据 Content-Encoding:gzip 进行解压,这样虽然稍微占用了一些服务器和客户端的CPU,但是换来的是更高的带宽利用率。对于纯文本来讲,压缩率是相当可观的。
  • 2,HTML压缩
  • 3,JS压缩 混淆
  • 4,CSS压缩
  • 5,图片压缩,展示尺寸和图片尺寸吻合

四、HTML代码结构优化

1,正确布置行内脚本

  • 尽可能使用外部脚本和样式文件
  • 脚本尽可能移到底部
  • 脚本放在顶部带来的问题,
    1) 使用脚本时,对于位于脚本以下的内容,逐步呈现将被阻塞
    2) 在下载脚本时会阻塞并行下载
    放在底部可能会出现JS错误问题,当脚本没加载进来,用户就触发脚本事件。所以要综合考虑情况。
  • Script延迟加载 defer属性(IE & FF3.1+)、setTimeout
  • 风险:行内(内联)脚本在样式表后面。
    所有主流浏览器都会保持CSS和JavaScript的顺序。在样式表完全下载、解析及应用之后,内联脚本才能执行。同时,必须在内联脚本执行后,剩余资源才能下载。
    CSS的下载解析可以和其他资源并发执行。

2,少用iframe

优点:可以和主页面并行加载
缺点: iframe会阻塞onload事件 解决:onload事件后设置iframe的src,或者JS创建iframe节点
和主页面使用同一个连接池
避免src为空—为空默认为主页面地址

3,减少DOM结构的层级
DOM层级越深会增加 CSS rule Tree 和 Dom Tree 匹配构造的性能
4,减少Cookie的大小
5,尽量用div取代table,或者将table打破成嵌套层次深的结构
table会影响页面呈现的速度,只有table里的内容全部加载完才会显示。

五、组件分成多个域

主要的目的是提高页面组件并行下载能力。但不要跨太多域名,建议采用2个子域名。

六、图片懒加载
七、图片,脚本,数据 预加载

八、图片base64
九、根据业务实际情况优化,保证首屏加载时间。
前端优化可以避免我们造成无谓的服务器和带宽资源浪费,但随着网站访问量的增加,仅靠前端优化已经不能解决所有问题了,后端程序处理并发请求的能力、程序运行的效率、硬件性能以及系统的可扩展性,将成为影响网站性能和稳定的关键瓶颈所在。

13XSS(Cross Site Scripting)跨站脚本攻击原理

原理

HTML是一种超文本标记语言,通过将一些字符特殊地对待来区别文本和标记,例如,小于符号(<)被看作是HTML标签的开始,之间的字符是页面的标题等等。当动态页面中插入的内容含有这些特殊字符(如<)时,用户浏览器会将其误认为是插入了HTML标签,当这些HTML标签引入了一段JavaScript脚本时,这些脚本程序就将会在用户浏览器中执行。所以,当这些特殊字符不能被动态页面检查或检查出现失误时,就将会产生XSS漏洞。 XSS攻击

XSS(Cross Site Scripting)跨站脚本攻击?

1. XSS 有常见注入的方法:

  • 在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
  • 在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
  • 在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
  • 在标签的 href、src 等属性中,包含 javascript: 等可执行代码。
  • 在 onload、onerror、onclick 等事件中,注入不受控制代码。
  • 在 style 属性和标签中,包含类似 background-image:url("javascript:..."); 的代码(新版本浏览器已经可以防范)。
  • 在 style 属性和标签中,包含类似 expression(...) 的 CSS 表达式代码(新版本浏览器已经可以防范)。

2. 在处理输入时,以下内容都不可信:

  • 来自用户的 UGC 信息
  • 来自第三方的链接
  • URL 参数
  • POST 参数
  • Referer(可能来自不可信的来源)
  • Cookie (可能来自其他子域注入)

存储型 XSS

  1. 攻击者将恶意代码提交到目标网站的数据库中。
  2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 在部分情况下,恶意代码加载外部的代码,用于执行更复杂的逻辑。
  5. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

反射型 XSS

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的  URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 在部分情况下,恶意代码加载外部的代码,用于执行更复杂的逻辑
  5. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

DOM 型 XSS

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL。
  3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
  4. 在部分情况下,恶意代码加载外部的代码,用于执行更复杂的逻辑。
  5. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

 

 

14.网络安全中 CSRF攻击原理与常见解决方案

什么是CSRF

跨站请求伪造(Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web上执行非本意的操作的攻击方法。

简单来说,就是冒用你的登录信息,以你的名义发送恶意请求。

原理

  1. 用户A登录了自己常用的网站 www.yinhang.com, 登录成功后,cookie中会包含A的登录信息
  2. 骗子 利用广告或者链接等诱导你打开网站 www.jiamao.com。该网站中会利用form表单或者其他方式,向网站 www.yinhang.com 发送请求,网站的接口请求格式一般很容易获取到,例如 www.yinhang.com/api/zhuanzhang?account=A&count=10000&to=pianzi 发送的请求会自动携带上www.yinhang.com的cookie信息,即你的身份验证信息
  3. 服务器端在收到请求后,检验身份信息是正确的,然后整个攻击就完成了

常见解决方案

referer首部

在request header中,有个referer字段,表明请求的来源,服务端可以通过这个字段判断是否是在合法的网站下发送的请求。

缺陷:Referer的值是由浏览器提供的,不同的浏览器实现不同,无法确保某些浏览器是否有安全漏洞,导致可以自行修改Referer字段

表单 token检测

由服务端生成一个token,返回给前端,在每次发送请求中,在参数中额外参加一个参数,例如 _csrf_token , 服务端每次校验时对比 _csrf_token 与服务端存储的值。

fetch(path, {

    ...otherParam,

    _csrf_token: 'token_value'

});

 

或者服务端返回的表单页面中隐藏 _csrf_token值

<form method="POST" action="/upload" enctype="multipart/form-data">

    <input name="_csrf_token" value="{{由服务端生成}}" style="display: none" />

    用户名: <input name="name" />

    <button type="submit">提交</button>

</form>

缺陷: 需要手动传递参数值,比较冗余麻烦,额外的参数字段也没有业务意义

header中增加csrf_token

由后端生成csrf_token设置在cookie中, 前端获取到token后设置在header中,可以统一在封装request方法时写入,例如

 

let csrfToken = Cookies.get('csrfToken');

 

$.ajaxSetup({

  beforeSend: function(xhr, settings) {

      xhr.setRequestHeader('x-csrf-token', csrfToken);

  }

});

15.Http和Https的区别

·  https协议需要到CA (Certificate Authority,证书颁发机构)申请证书,一般免费证书较少,因而需要一定费用。

·  http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

·  http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

·  http的连接很简单,是无状态的。Https协议是由SSL+Http协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)

 

16浏览器的三次握手。

第一次握手:客户端发送网络包,服务端收到了。这时候服务端的都结论:客户端的发送能力、服务端的接受能力正常。

第二次握手:服务端收到网络包会给客户端响应,这时候服务端发送网络包,客户端收到了,此时的服务端得出结论:服务端的发送能力没有问题,因为客户端没有给服务端响应。

第三次握手:客户端收到网络包后,给服务端响应,这时候客户端给服务端发送网络包,服务端收到了,此时服务端得出结论:客户端的发送、接受能力没有问题,自己的发送,接受能力也没有问题。

17.浏览器的四次挥手

建立一个连接需要三次握手,而终止一个连接要经过四次挥手,这是由于TCP的半关闭造成的,所谓的半关闭就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。

TCP 的连接的拆除需要发送四个包,因此称为四次挥手,客户端或服务端均可主动发起挥手动作。

  • 第一次挥手:客户端A发送一个FIN.用来关闭客户A到服务器B的数据传送
  • 第二次挥手:服务器B收到这个FIN. 它发回一个ACK,确认序号为收到的序号+1。和SYN一样,一个FIN将占用一个序号
  • 第三次挥手:服务器B关闭与客户端A的连接,发送一个FIN给客户端A
  • 第四次挥手:客户端A发回ACK报文确认,并将确认序号设置为序号加1

18.浏览器渲染过程?

1. 浏览器把获取到的html代码解析成1个Dom树,html中的每个tag都是Dom树中的1个节点,根节点就是我们常用的document对象 。dom树里面包含了所有的html tag,包括display:none隐藏,还有用JS动态添加的元素等。

 

 2. 浏览器把所有样式(主要包括css和浏览器的样式设置)解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式,比如IE会去掉-moz开头的样式.

 

 3. dom tree和样式结构体(cssom)结合后构建呈现树(render tree),render tree有点类似于dom tree,但其实区别有很大,render tree能识别样式,render tree中每个node都有自己的style,而且render tree不包含隐藏的节点(比如display:none的节点,还有head节点),因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到 render tree中。

注意: visibility:hidden隐藏的元素还是会包含到render tree中的,因visibility:hidden 会影响布局(layout),会占有空间。

1.1 dom树结构,cssom,renderTree

 

CSSOM  :  样式表解析完毕后,系统会根据选择器将CSS规则添加到对应的哈希表中。这些哈希表包括:ID哈希表、类名称哈希表、标记名哈希表等,还有一种通用哈希表,适合不属于上述类别的规则。如果选择器是ID,规则就会添加到ID表中;如果选择器是类,规则就会添加到类表中。

 

注意:此处的cssom(样式结构体)尚有疑问。

 

1.2 cssom和dom树怎么匹配结合成renderTree ?

     (以dom节点匹配cssom)

 

       从本渲染结点开始,判断此结点是否与选择器链表的当前选择器相匹配。如果匹配,判断此选择器与下一个选择器的关系:如果为NONE,表示本选择器是选择器链的最后一个,返回成功;如果关系为AND (比如:#id.class),选择下一个选择器与本渲染结点继续比较;如果关系为CHILD,表示本选择器是下一个选择器的子结点,返回下一个选择器与下一个渲染结点的匹配结果;否则,关系为DESCENDANT,选择器和渲染结点各指向下一个结点,然后将渲染结点继续回溯,直到第一个满足回溯后的选择器的结点,此时将继续判断回溯后的选择器和回溯后的渲染结点是否匹配。

 

19.什么是重绘?回流(重排)?

1. 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流。每个页面至少需要一次回流,就是在页面第一次加载的时候。

 

2. 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。

 

注:回流必将引起重绘,而重绘不一定会引起回流。

20.什么是重绘?回流(重排)?

---不同的条件下发生重排的范围及程度会不同 :

 

1.页面初始渲染

 

2.改变字体,改变元素尺寸(宽、高、内外边距、边框,改变元素位置等

    各种情况:

             设置 style 属性的值 

             激活 CSS 伪类,比如 :hover

            操作 class 属性

            css3的某些属性(https://csstriggers.com/  结合此链接查看哪些属性会触发重排、哪些属性会触发重绘以及哪些属性会触发合成;)

(注意:如果修改属性不影响布局则不会发生重排)

3..改变元素内容(文本或图片等或比如用户在input框中输入文字)

4.添加/删除可见DOM元素(注意:如果是删除本身就display:none的元素不会发生重排;visibility:hidden的元素显示或隐藏不影响重排)

 

5.fixed定位的元素,在拖动滚动条的时候会一直回流

6. 调整窗口大小(Resizing the window)

7.计算 offsetWidth 和 offsetHeight 属性【注释2】

【注释2:flush队列】

浏览器是聪明的,当对以下属性进行操作的时候:

包括:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)。

浏览器不会马上操作它们,而是会先缓存在队列中,有一定时间顺序去执行这些操作,但是在这过程中我们需要去获取在该队列中的属性时,浏览器为取得正确的值就会触发重排。这样就使得浏览器的优化失效了。

所以,在多次使用这些值时应进行缓存。

如何减少回流,重绘

4.1.修改html元素中对应的class名,利用class替换样式

【代码示例】

<style type = "text/css" >

.changeStyle{width: 200px;height: 200px;}

</style >

< script type = "text/javascript" >

    $(document).ready(function ()

    {

        var el = $('id');

        //1

       el.css('width', '200px');

       el.css('height', '200px');

        //2

       el.css({

            'width' : '200px;',

            'height' : '200px;'

        });

        //3 

       el.addClass('changeStyle');

    });

</script >

 

4.2. csstext(利用cssText属性合并所有改变,然后一次性写入)

【代码示例】

var s = document.body.style; 

s.padding = "2px"; // 回流+重绘

s.border = "1px solid red"; // 再一次 回流+重绘 

s.color = "blue"; // 再一次重绘

s.backgroundColor = "#ccc"; // 再一次 重绘 

s.fontSize = "14px"; // 再一次 回流+重绘 

// 添加node,再一次 回流+重绘

document.body.appendChild(document.createTextNode('abc!’));

----------------------------------------------------------------------------

// 不好的写法

var left = 1;

var top = 1;

el.style.left = left + "px";

el.style.top = top + "px";

// 比较好的写法 

el.className += " className1";

// 比较好的写法 

el.style.cssText += "; left: " + left + "px; top: " + top + "px;";

4.3  display:none(隐藏元素,应用修改,重新显示)

【代码示例】

var list = document.getElementById("list");

list.style.display = 'none’;

appendDataToElement(list,data);

list.style.display = 'block';

4.4  cloneNode (将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后再替换回去; )

【代码示例】

var old = document.getElementById("list");

var clone = old.cloneNode(true);

appendDataToElement(clone,data);

old.parentNode.replaceChild(clone,old);

4.5.document.createDocumentFragment();(使用文档片段(document fragment)在当前DOM之外构建一个子树,再插回去)

【代码示例】

var fragment = document.createDocumentFragment();

var list = document.getElementById("list");

for (var i = 0; i< 10; i++)

{

    var _li = document.createElement("li");

    _li.onmouseover = function ()

    {

       this.style.backgroundColor = "#22b909";

       this.style.width = "120px";

       this.style.height = "50px";

    }

    _li.onmouseout = function ()

    {

       this.style.backgroundColor = "";

       this.style.width = "100px";

       this.style.height = "40px";

    }

   fragment.appendChild(_li);

}

list.appendChild(fragment);

 

4.6   使用trsansform

        CSS的最终表现分为以下四步:Recalculate Style -> Layout -> Paint Setup and Paint -> Composite Layers

        按照中文的意思大致是 查找并计算样式 -> 排布 -> 绘制 -> 组合层。

       由于transform是位于Composite Layers层,而width、left、margin等则是位于Layout层,在Layout层发生的改变必定导致Paint Setup and Paint -> Composite Layers,

       所以相对而言使用transform实现的动画效果肯定比使用改变位置(margin-left等)这些更加流畅。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值