前端学习笔记--JavaScript、Vue、Webpack

JavaScript

不一样数据类型间的比较

答案:true true falsejavascript

  • 解析:

第一个输出:将[]转为布尔类型,为true。只有如下类型转为布尔值为false:Boolean(null)、Boolean(undefined)、Boolean(0)、Boolean(‘’)、Boolean(NaN)css

第二和第三:其余类型与布尔类型比较,将两个类型转为数字再进行比较。Number([])=0,Number(false)=0,因此第二个输出true。Number({})=NaN,NaN==0 --> false,因此第三个输出falsehtml

Number([2]) //2
Number(['x']) //NaN
Number(undefined);  //NaN
Number(null);   //0
null+1=1
undefined+1=NaN

undefined==false;   //false
false==''   //true

null==undefined;    //true,都转为boolean类型
null=undefined;     //false,不强制转换
复制代码

FormData

  • 建立FormData对象
    • 像建立对象同样建立,无参数
    • 将已有的表单对象做为参数来建立FormData对象
HTML部分
<form action="" id='form'>
        <label for="">
            姓名: <input type="text" name="name">
        </label>
        <label for="">
            文件:<input id="file" type="file" name="file">
        </label>
        <label for="">
            <input type="button" value="保存">
        </label>
</form>
复制代码
JS部分
var form=document.getElementById('form');
var formdata=new FormData(form);
var name=formdata.get(name);
var file=formdata.get(file);
formdata.append('token','sdcsdc');
//提交数据
var xhr=new XMLHttpRequest();
    xhr.open("post","http://127.0.0.1/adv");
    xhr.send(formdata);
    xhr.onload=function(){
        if(xhr.readyState==4&&xhr.status==200){
            //...
        }
    }
复制代码
  • 往FormData对象添加数据 -- 数据类型是键值对
    • FormData.append( key,value [,filename] )
    • filename:当添加的数据是file对象就能够添加这个参数,用来设置传递给服务器的文件名称(可选参数)
var formdata=new FormData();
formdata.append('name','jack');
formdata.append('name','rose');

formdata.get('name');   //jack
formdata.getAll('name');    //[jack,rose]
复制代码
  • 获取数据
    • FormData.get(key)
    • FormData.getAll(key)
  • 判断是否存在某个值
    • FormData.has(key)
  • 遍历数据
    • FormData.keys():返回全部数据的键值
    • FormData.values():返回全部数据的值
    • FormData.entries():返回FormData对象的迭代器
formData.append("k1", "v1");
formData.append("k1", "v2");
formData.append("k2", "v1");

var i = formData.entries();

i.next(); // {done:false, value:["k1", "v1"]}
i.next(); // {done:fase, value:["k1", "v2"]}
i.next(); // {done:fase, value:["k2", "v1"]}
i.next(); // {done:true, value:undefined}

//返回的对象的value属性以数组的形式,第一个是key,第二个为value

//以循环的形式
for(var pair of formData.entries()){    //pair是每次迭代获取到的对象里的value
    console.log(pair[0]+':'+pair[1])
    //k1:v1
    //k1:v2
    //k2:v1
}
复制代码
  • 修改数据
    • FormData.set(key,value) :若key不存在会自动建立
  • 删除数据
    • FormData.delete(key):即便key不存在也不会出错

js垃圾回收机制

  • 标记清除:给存储在内存中的变量都添加标记,而后去掉那些处在环境中的变量和被环境中的变量引用的变量的标记,剩下被标记的就是将要删除的变量。而后垃圾回收机制到下一个周期运行时,将释放这些变量的内存,回收他们占用的空间。
  • 引用计数:语言引擎有一张引用表,保存内存里全部资源的引用次数。若是一个值的引用次数是0,则表示这个值再也不用到了,所以能够将这块内存释放。

js垃圾回收机制前端

线程与进程的区别

  • 进程是CPU资源分配的最小单位
  • 线程是程序执行时的最小单位,是CPU调度和分派的最小单位

  • 一个程序至少有一个进程,一个进程至少有一个线程。
  • 一个进程能够由多个线程组成,线程间共享进程的全部资源,每一个线程有本身的局部变量和堆栈,而进程有本身的独立地址空间。
  • 线程由CPU独立调度执行,多CPU环境下就容许多个线程同时执行。
  • 单线程(单个CPU运行):好比,打开任意一个浏览器,里面有多个标签页;当某一个标签页系统崩溃时,其余标签页也不能使用,必须关掉浏览器。
  • 多线程(多个cpu等运行):好比,浏览器中有多个标签页,当某一个标签页系统崩溃时,只是该标签页不能使用,不影响其余标签页的正常使用。

A:-1 B:-2 C:0 D:1

答案:vue

  • 每一个线程对a均作了两次操做:+1,-2
  • 当线程1与线程2不并发时:1执行完后a=-1,2使用-1做为a的初值,执行完后a=-2
  • 当线程1与线程2并发时:此时读写冲突,至关于只有一个线程对a的读写最终生效,结果a=-1
  • 当线程1与线程2部分并发:1执行到第一个操做时,a=1。此时线程2开始。1的读写被2覆盖,2把a=1做为初值,结果为0

逗号运算符,=号运行符,做用域

下面输出结果是:java

var out = 25,
   inner = {
        out: 20,
        func: function () {
            var out = 30;
            return this.out;
        }
    };
console.log((inner.func, inner.func)());
console.log(inner.func());
console.log((inner.func)());
console.log((inner.func = inner.func)());
复制代码

结果:25 20 20 25node

  • 第一个输出:逗号运算符是运算前面的,返回最后一个的结果,此时返回的是最后一个inner.func的结果,便是一个匿名函数。

例子webpack

let a=1,b=2,k=0;
console.log( (a++,b++,k++) );   //输出0,注意是要把整个括起来,而后将计算后的结果返回
console.log(a); //2
console.log(b); //3
console.log(k); //1
复制代码
  • 第二个输出和第三个输出:都是对象调用函数,函数的上下文this是对象
  • 第四个输出:等号运算符返回的是赋值后的结果,即返回一个匿名函数

例子:ios

let a=3,b;
console.log(b=a);   //输出3
复制代码

引用类型的赋值,等号运算符与.运算符的优先级问题

var a = {n: 1};
var b = a;
a.x = a = {n: 2};

console.log(a.x);
console.log(b.x);
复制代码

答案:nginx

undefined
{n:2}
复制代码

分析:

  • b和a指向同一地址,对象{n:1}的地址
  • 在a.x = a = {n: 2};中,因为.号运算符优先级更高,因此先进行a.x
  • 那到底是a.x=a,a={n:2}仍是a = {n: 2} && a.x = {n:2} 仍是 a.x = {n:2} && a= {n:2}。借助Proxy来讲明:
const obj = new Proxy({}, {
  set(target, key, value, r) {
    console.log(key, value)
    if (key === 'a') Reflect.set(target, key, 'isA', r);
    else Reflect.set(target, key, value, r);
  }
});

obj.b = obj.a= {n: 1};
// 输出:
// "a" {n: 1}
// "b" {n: 1}

obj.a; // isA
obj.b; // {n: 1}
复制代码
能够得出 赋值的顺序是从右边开始到左边的。
并且是直接 a = {n: 1}, a.x = {n:1 },而不是 a.x = a 这样去赋值
复制代码

再借助 Proxy 来分析一开始part1这道题,用obj.a, obj.b 来代替原题目的 a和b。

var obj = new Proxy({}, {
        get: function (target, key, receiver) {
            console.log(`getting ${key}!`);
            return Reflect.get(target, key, receiver);
        },
        set: function (target, key, value, receiver) {
            console.log(`setting ${key}!`);
            return Reflect.set(target, key, value, receiver);
        }
    });

    obj.a = {n: 1 };// setting a;
    obj.b = obj.a; // getting a; setting b;
    obj.a.x = obj.a = {n:2 }; // getting a; setting a;
    
*****************分割线**********************************    
    console.log(obj.a);     //{n;2}
    obj.a.n=3;          //getting a
    console.log(obj.b); //getting b {n:1,x:{n:3}}
复制代码
第一部分:
能够看到obj.a.x = obj.a = {n: 2}这段语句执行时,会先输出一个 getting a 再输出 setting a。
这就意味着在对 obj.a.x 赋值时,程序是先获取 obj.a指向的对象的内存地址,此时触发了 getting a,
而后再对右边 obj.a 进行赋值,触发了 setting a, 赋值完最后一步才是对 obj.a.x赋值 {n:2 }。
注意:最后一步对obj.a.x赋值时,不会再去读取obj的a,则说明此时的a还是指向原来的地址-- 对象{n:1}的地址,也是obj.b指向的地址。
赋值以后,此时obj.a(和obj.b再也不指向同一地址)和obj.b.x指向的是同一地址 -- {n:2}的地址
复制代码
第二部分:
在分割线之下,obj.a.n=3; 读取的a再赋值,改变了指向的对象{n:2}的值
而obj.b.x也指向该对象
复制代码

参考

变量提高,(字符串/变量 in obj)的含义

if (!("a" in window)) {
    var a = 1;
}
alert(a);   //undefined
复制代码
  • "a" in window:意思是window对象是否有a这个属性,写成a in window也能够

此时代码的意思是,若是没有这个属性,则建立这个属性且赋值。

但if不是代码块,存在变量提高,因此上面代码实际为

var a;
if (!("a" in window)) {
    a = 1;
}
alert(a); 
复制代码

做用域

函数内属于一个做用域,try和catch不属于一个做用域

(function f() {
        try{
           throw new Error()
        }catch (x) {
            var x=1,y=2;
            var t=3;
            console.log(x);
        }
        console.log(t); //3
        console.log(x); //undefined
        console.log(y); //2
    })();
复制代码
在f函数中,x、y、t会变量提高
在catch中,var x=1,就近原则,此时对x的赋值实际上是对形参的赋值,不是f函数里的x,因此函数f的x在catch外访问依旧是undefined
复制代码

函数声明、函数表达式

声明变量b是一个函数,使用函数表达式

一、let b=function(){}

二、let b=function f(){} //能够为函数起一个名字,可是'f'不属于window的属性,则没法调用f(),至关于匿名

下面程序的输出结果?

var a = 1;
var b = function a(x) {
  x && a(--x);
};
alert(a);   //1
console.log(b(a))  //undefined 由于函数没有return,有return的话就是0

/* 在函数外部,a是number类型,在外面调用函数a会出错,但在函数内部调用不会出错 上面其实等同于 var b=function(x){...} */
复制代码
  • 函数声明和变量声明都存在提高,但函数表达式不会,只会提高引用的变量。
  • 若是函数名和变量重名,函数声明会覆盖变量声明,但赋值不会,谁后就是那个值。即函数声明级别比变量声明高。

例子:

function a(){console.log('function')}
var a;
console.log(typeof a)   //function

//若是变量赋值了
function a(){console.log('function')}
var a=1;
console.log(typeof a)   //number
复制代码

函数内的变量提高,与全局变量重名

var a = 4;
    function b() {
        a = 3;
        console.log(a);
        function a(){};
    }
    b();    //3
    console.log(a); //4
复制代码

函数b内的函数a会提高,而后被a=3覆盖了值,因此输出3

挖坑:

var a = 4;
    function b() {
        a = 3;
        
    }
console.log(a); //4
复制代码

为何不是输出3?—— 由于没有调用b()....

数据类型

  • 基本数据类型:string , number , boolean , undefined , null , symbol(ES6)
  • 引用类型:对象、数组、函数

typeof返回的数据类型

string、number、boolean、undefined、object、function、symbol

null与undefined

  • null指的是一个对象为空,即变量是有值的,值为null
  • undefined:表示的是一个变量/属性没有设置值。
  • null==undefinde —— true, null===undefinde —— false

JSON.stringify转化null与undefined

JSON.stringify({1:undefined}) //“{}”
JSON.stringify({0:null})    //“{“0”:null}”
复制代码

返回undefined的状况

  • 当一个变量未赋值
  • 访问对象的某个属性不存在
  • 函数没有返回值
  • 函数应该传递的参数没有提供,该参数等于undefined

symbol

  • symbol值是经过Symbol函数生成,没有new,生成的Symbol是一个相似于字符串的原始类型的值。
  • Symbol(para):para能够是字符串/对象,对象会调用toString方法转为字符串
  • 传入的参数只是对当前Symbol值的描述,不是返回的值,因此即便传入相同的参数,Symbol的返回值也是不一样的
var sm=Symbol('xx');
console.log(sm);   //Symbol('xx')
typeof sm;  //'symbol'

var ss1=Symbol('xx');
console.log(ss==ss1);  //false
ss=ss1;     //由于是基本数据类型,因此能够用=令两个值相等
console.log(ss===ss1);   //true 
复制代码

js常见的内置对象

object、String、Number、Boolean、Math、Array、Function、JSON

如何理解JSON

  • JSON是JS一个内置对象,含有两个函数,stringify和parse
  • 同时JSON也是一种轻量级的数据交换格式,以{}括起来
  • JSON 与 JS 对象的关系:JSON是JS对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。
  • 任何支持的类型均可以经过 JSON 来表示,例如字符串、数字、对象、数组等。

JS的内置函数

String、Number、Boolean、Object、Function、RegExp、Error、Date、Array

日期Date

Date.now():返回当前时间毫秒数

var dt=new Date();
dt.getFullYear();   //年
dt.getMonth();      //月(0-11)
dt.getDate();       //日(1-31),返回一个月中的某一天

dt.getDay();        //天(0-6),返回一星期中的第几天(0表示星期日)

dt.getHours()       //小时(0-23)
dt.getMinutes()     //分钟(0-59)
dt.getSeconds()     //秒数(0-59)

dt.getTime()        //返回从1970/1/1 至今的毫秒数

##有对应的set方法
复制代码

对象的键

var a={},
b={key:'b'},
c={key:'c'};

a[b]=123;
a[c]=456;

console.log(a[b]);  //456
复制代码

解析:

由于键的名称只能是字符串,b/c做为键会调用toString获得的都是[object Object],因此a[b]和a[c]都是a["[object Object]"]

复制代码

this

  • 函数直接加()调用,this=window
  • 对象打点调用实例函数,this是这个对象
  • 从数组/类数组中枚举出函数(即函数是数组的元素)调用,this是这个数组
function fun1(fn){  //第二步
    arguments[0](3,4);  //类数组枚举调用传入的fun2(3,4)
}
function fun2(){
    alert(this.length); //此时this是fun1的arguments,表示fun1传入的实参个数--5
    alert(arguments.length); //此时表示fun2传入的实参个数-- 2
}
fun1(fun2,5,6,7,8); //第一步
复制代码
  • 定时器的回调函数,this=window
  • 事件处理函数,this是绑定事件的元素
  • 用new调用函数,this是建立的实例对象
  • call、apply绑定this,若是没有传参或传的是null,则this指向window

setTimeOut

for(var i=0;i<5;i++){
    setTimeout(console.log(i),0)    //第一个参数不是函数
    //0
    //1
    //2
    //3
    //4
}
复制代码

数组的方法

  • 对于那些遍历数组的每一项,而后每一项都去执行回调的方法,都要有return,除了forEach

数组的方法

  • lastIndexOf(val [,startIndex]):若是未传入第二个参数,则从尾向头查找第一次出现val的位置,若是指定了第二个参数,则从这个参数向前找。用这个方法能够找某个元素 在该数组最后一次出现的位置,由于是从后往前找。

parseInt

parseInt(string [,radix]):解析一个字符串参数,并返回一个指定基数的整数

参数:

  • string:要被解析的值,若是参数不是一个字符串,则将其转换为字符串,使用toString,字符串开头的空白符会被忽略。
  • radix:一个介于2-36之间的整数,表示把第一个参数看做是一个数的几进制表示,默认为10

返回值:

  • 返回的是第一个参数的十进制表示
若是radix为undefined或0或未指定,
一、若是string以‘0x’或‘0X’开头,则基数是16(16进制)
二、若是string以‘0’开头,则基数是8或10进制,具体是哪一个进制由实现环境决定
三、若是字符串string以其余任何值开头,则基数是10
复制代码

例子:

['10','10','10','10','10'].map(parseInt);
// [10, NaN, 2, 3, 4]
复制代码

实际执行的代码是:

['10','10','10','10','10'].map((item, index) => {
	return parseInt(item, index)
})
//第一个radix为0,属于上面所说的状况,此时看item,不是以0或0x开头,则基数是10,10的十进制就是10

//第二个 10,radix是1,表示10是1进制表示的数字,错误
....
复制代码

异步过程

1)主线程发起一个异步请求

2)相应的工做线程接收到请求后告知主线程已收到(异步函数返回)

3)主线程能够继续执行下面的代码,同时工做线程执行异步任务。

4)等到工做线程完成任务后,通知主线程

5)主线程收到通知后,执行必定动做(调用回调函数)。

JS引擎中负责解释和运行js代码的只有一个,叫作主线程,还有其余线程,例如处理Ajax请求的线程,处理DOM事件的线程,定时器线程等工做线程(可能存在与js引擎内或外)。

综上,能够总结为两个要素:

一、发起函数(注册函数)-- 用来发起异步过程

二、执行回调函数 -- 用来处理结果

两个函数都是在主线程上调用的


异步任务执行完毕后须要通知主线程,这个通知机制是如何实现的?—— 消息队列和事件循环

异步任务执行完毕后,工做线程将回调函数封装成消息message放到消息队列中,主线程经过事件循环过程去取出消息

  • 消息队列:是一个先进先出的队列,存放各类消息
  • 事件循环:指的是主线程重复的从消息队列里取出消息并执行的过程,取出一个消息并执行的过程就叫作一次循环。
    • 同步和异步任务分别进入不一样的执行场所,同步进入主线程,异步进入Event table并注册回调函数
    • 异步任务执行完毕后,会将注册的函数加入事件队列里
    • 当主线程内的任务执行完毕后,会去事件队列里读取消息,到主线程执行回调函数
    • 重复以上过程就是事件循环

例子:

setTimeout(fn,2000)
复制代码

setTimeout就是发起异步过程的函数,fn就是回调函数,可是回调函数并不必定要做为发起函数的参数,如:

var xhr = new XMLHttpRequest(); 
xhr.onreadystatechange = xxx; // 添加回调函数
xhr.open('GET', url);  //发起异步请求的函数
xhr.send(); // 发起函数
复制代码

宏任务和微任务

除了广义的同步任务和异步任务以外,还有宏任务和微任务

  • 宏任务:总体的js代码、setTimeout、setInterval、setImmediate
  • 微任务:promise.then,process.nextTick(优先级比promise.then高,比它先执行)

不一样的任务会进入不一样的事件/任务队列,好比setTimeoutsetInterval会进入相同的Event Queue。

先执行宏任务队列的一个,而后再执行宏任务下的全部微任务,再去渲染,完成一次事件循环。而后再执行下一个宏任务。

事件循环的一次循环,就是执行事件队列里的一个任务。先执行宏任务,再执行宏任务里的微任务,这样就是一次循环,第一轮循环执行后,在执行下一次循环

例子

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
复制代码

结果:1,7,6,8,2,4,3,5,9,11,10,12。

第一轮事件循环:

  • 整个js代码做为第一个宏任务进入事件队列里,遇到console.log,输出1。
  • 遇到setTimeout,属于异步任务,等到异步任务完成后,回调函数被分发到宏任务的事件队列里,咱们暂时标记为setTimeout1
  • process.nextTick(),其回调函数被分发到微任务Event Queue中。咱们记为process1。
  • 遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。咱们记为then1。
  • 又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,咱们记为setTimeout2。此时宏任务队列里就有两个回调函数了。
  • 第一轮循环的宏任务执行完后,有两个微任务,process1和then1
  • 执行process1,输出6。
  • 执行then1,输出8。

第二轮事件循环:

  • 执行第二个宏任务,从setTimeout1宏任务开始,输出2
  • 接下来遇到了process.nextTick(),一样将其分发到微任务Event Queue中,记为process2。
  • new Promise当即执行输出4,then也分发到微任务Event Queue中,记为then2。
  • 第二轮事件循环宏任务结束,咱们发现有process2和then2两个微任务能够执行
  • 输出3
  • 输出5

第三轮事件循环:

  • 只剩setTimeout2了,执行。输出9。
  • 将process.nextTick()分发到微任务Event Queue中。记为process3。
  • 直接执行new Promise,输出11。 将then分发到微任务Event Queue中,记为then3。
  • 第三轮事件循环宏任务执行结束,执行两个微任务process3和then3
  • 输出10
  • 输出12

例子

process.nextTick(() => {    //微任务
  console.log('nextTick')
})
Promise.resolve()
  .then(() => {     //微任务
    console.log('then')
  })
setImmediate(() => {    //宏任务
  console.log('setImmediate')
})
console.log('end')
复制代码

运行结果:

end
nextTick
then
setImmediate
复制代码

微任务里的微任务

process.nextTick(()=>{
    console.log(1)
    process.nextTick(()=>{
        console.log(2)
    })
})
process.nextTick(()=>{
    console.log(3)
})
console.log('run')
复制代码
run
1
3
2
复制代码

详解

promise

promise是异步编程的解决方案,比传统的异步编程解决方案【事件】和【回调函数】更加合理和强大。

promise对象用于异步操做,它表示未完成且预计将来完成的异步操做

  • promise对象能够经过Promise构造函数建立,Promise构造函数须要传递一个函数做为参数,当建立Promise实例时,则会调用该函数。
  • 这个函数参数有两个参数,resolve和reject函数,用来改变promise对象的状态。
  • promise有三个状态,分别是pending、fulfilled和rejected。只能从pending转为fulfilled或rejected,状态肯定以后不可改变
  • promise原型上有then函数,传递的参数是fulfilled状态和rejected状态执行的回调,回调的参数就是promise的值,则resolve和reject函数的实参
  • then函数返回一个新的promise实例,该实例的状态与回调函数的执行状况和返回值有关。默认返回一个resolved状态。若是抛出错误,则返回的promise实例就是rejected状态。若是返回一个新的promise实例,则取决于这个promise的状态,等待它状态改变才会执行下面的then的回调
  • promise原型上还有catch方法,能够捕获then回调抛出的错误
  • 原型上还有race方法,能够传入多个promise任务,多个任务同步执行,最终返回第一个执行结束的promise任务的结果
  • 原型上还有all方法,只有全部的promise任务都执行成功才会调用成功的回调,只要有一个失败则调用失败的回调
  • 原型上还有resolve,用来建立promise实例。
    • Promise.resolve()能够传一个普通值,返回一个resolved类型的promise实例
    • 能够传入一个promise实例,返回的就是这个promise实例
    • 能够传入thenable对象,返回的promise实例的状态取决于这个对象的最终状态
  • 原型上还有reject方法,也是用来建立promise对象,不管传入什么值,返回的都是一个rejected类型的promise实例

在没有 promise 以前,怎么解决异步回调

  • 回调函数
  • 事件监听
  • 订阅发布模式 资料

async与await

使异步变同步

  • async定义的函数表示该函数是一个异步函数,调用该函数时,不会阻塞其余代码的运行
  • 当async会封装一个由Promise.resolve()建立的Promise实例,而后返回。若是抛出错误,则封装一个Promise.reject()返回。若是显式写了return一个值,那这个值就是返回的promise实例的值。
  • 若是要获取Promise的值,须要写then函数,传入回调
  • async函数不必定须要await,但await必须包含在async内
  • await表示等待的意思,等待后面的代码返回结果以后再继续指向。
  • await后面能够是任何表达式,但一般是一个返回Promise实例的表达式
async function timeout() {
    return 'hello world'
}
console.log(timeout());
console.log('虽然在后面,可是我先执行');
复制代码

结果

Promise {<resolved>: "hello world"}
test.html:85 虽然在后面,可是我先执行
复制代码

获取Promise的值

async function timeout() {
    return 'hello world'
}
timeout().then(result => {
    console.log(result);
})
console.log('虽然在后面,可是我先执行');
复制代码

结果

虽然在后面,可是我先执行
hello world
复制代码

await

function doubleAfter2seconds(num) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2 * num)
        }, 2000);
    } )
}

async function testResult() {
    let result = await doubleAfter2seconds(30);
    console.log(result); //60
}

testResult();   
复制代码

Ajax

var xhr=new XMLHttpRequest();
xhr.open('get','/index.js',true)    //true表示异步
xhr.onreadystatechange=function () {
    if(xhr.readyState===4)
        if(xhr.status===200){
            console.log(xhr.responseText)
        }
}
xhr.send(null); //将请求发送给服务端,仅用于post请求
复制代码
xhr.open()的第三个参数表示是否异步
XMLHttpRequest 对象若是要用于 AJAX 的话,其 open() 方法的 async 参数必须设置为 true

xhr.send():当使用初始化)send方法还未调用
1:(载入)已调用send方法,正在发送请求
2:(载入完成)send方法已经执行完毕,已经接收到全部响应内容
3:(交互)正在解析响应内容
4:(完成)响应内容解析完成,能够在客户端调用
复制代码

如何保证cookie的安全性

  • 对保存到cookie的私密数据进行加密
  • 设置http-only=true
  • 给cookie设置有效期
  • 给cookie加个时间戳和ip戳,实际就是让cookie在同个ip下过多少时间后失效

cookie、localStorage、sessionStorage的区别

cookie

  • 由于HTTP是无状态的,对事务没有记忆能力。cookie是网站用来标识用户身份的(好比存储用户信息),存储在本地的数据。
  • 在第一次请求服务端时,服务端在响应报文头添加set-cookie属性,属性值便是用户的cookie值。浏览器在下次请求时会自动在请求报文头加上Cookie属性,属性值即由服务端产生。
  • cookie数据始终在同源的HTTP请求中携带(即便不须要),在浏览器和服务端之间来回传输

storage

  • sessionStorage和localStorage都是在本地保存,不会发送给服务端

共同点:

  • 都是保存在客户端,且同源的。

数据有效时间

  • localStorage是永久性保存,除非手动删除
  • sessionStorage保存的数据在当前窗口关闭后自动删除
  • cookie数据在cookie设置的过时时间前一直有效,即便关闭窗口或浏览器

做用域不一样:

  • sessionStorage在不一样的浏览器窗口不共享
  • cookie和localStorage在全部同源窗口共享

cookie和session的区别

  • cookie是保存在客户端,服务器可以知道其中的信息
  • session是保存在服务端,客户端不知道其中的信息
  • cookie保存的是字符串,session保存的是对象
  • session不能设置路径,同一个用户在访问一个网站的期间,全部的session在任何一个地方都能访问到
  • cookie能设置路径参数,那么同一个网站的不一样路径下的cookie互相是访问不到的
  • Session的实现是基于Cookie:Session技术是将数据存储在服务器端的技术,会为每一个客户端都建立一块内存空间 存储客户的数据,但客户端须要每次都携带一个标识ID去服务器中寻找属于本身的内存空间。

从敲入 URL 到渲染完成的整个过程

  • 用户输入url地址,浏览器根据DNS解析域名,获得IP地址
  • 通过三次握手创建TCP链接
  • 浏览器向服务器发送HTTP请求
  • 服务端接收请求,将html文档返回给浏览器
  • 浏览器接收文档后,若是有压缩则解压处理,而后就是页面解析
  • 浏览器将文档下载下来后,便开始从上到下解析,解析完成以后,会生成DOM。若是页面中有css,会根据css的内容造成CSSOM,而后DOM和CSSOM会生成一个渲染树,最后浏览器会根据渲染树的内容计算出各个节点在页面中的确切大小和位置,并将其绘制在浏览器上。
  • 若渲染过程遇到js文件,html文档会挂起渲染的线程,等待js文件加载和解析完毕,才可恢复html文档的渲染进程。

DOMContentLoaded和onload的区别

DOMContentLoaded:顾名思义,就是dom内容加载完毕。那什么是dom内容加载完毕呢?咱们从打开一个网页提及。当输入一个URL,页面的展现首先是空白的,而后过一会,页面会展现出内容,可是页面的有些资源好比说图片资源还没法看到,此时页面是能够正常的交互,过一段时间后,图片才完成显示在页面。从页面空白到展现出页面内容,会触发DOMContentLoaded事件。而这段时间就是HTML文档被加载和解析完成。

onload:页面上全部的资源(图片,音频,视频等)被加载之后才会触发load事件

对url进行编码的函数:escape,encodeURI,encodeURIComponent

  • escape(url):该方法不会对 ASCII 字母和数字进行编码,也不会对下面这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。其余全部的字符都会被16进制的转义序列替换。 所以若是想对URL编码,最好不要使用此方法。
  • encodeURI(url):该方法的目的是对 URI 进行完整的编码,所以对如下在 URI 中具备特殊含义的 ASCII 标点符号,encodeURI() 函数是不会进行转义的:;/?:@&=+$,#
  • 该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。 其余字符(好比 :;/?:@&=+$,# 这些用于分隔 URI 组件的标点符号),都是由一个或多个十六进制的转义序列替换的。 因此能够用来将链接组件(协议、主机号、端口号)间的符号转换
var str='https://www.bilibili.com/text.html'
    console.log(escape(str));
    console.log(encodeURI(str));
    console.log(encodeURIComponent(str));
    
encodeURI("http://www.w3school.com.cn/My first/")    
复制代码

输出

https%3A//www.bilibili.com/text.html
test.html:59 https://www.bilibili.com/text.html
test.html:60 https%3A%2F%2Fwww.bilibili.com%2Ftext.html

http://www.w3school.com.cn/My%20first/
复制代码

性能优化:将js放到body标签底部

  • 问题: js会阻塞文档的解析,页面的渲染,但生成完整的dom树是须要读完整个文档,那script不管放在哪都同样
  • 实际: 浏览器为了更好的用户体验,浏览器可以渲染不完整的dom树和cssDom,尽快减小白屏的时间,这就是浏览器的first paint。
  • 总结: 因此若是把script标签放在文档头部,js会阻塞后续文档的加载,阻塞解析dom,阻塞first paint,致使白屏时间延长,可是不会减小DOMContentLoaded被触发的时间。

参考

document.write 和 innerHTML 的区别

  • 若是想获取document,使用document.documentElement
  • document.write是重写整个document内容
  • innerHTML是HTMLElement的属性,能够精确到一个具体的元素,能读和写一个元素内部的html内容

script 标签的 defer 和 async 标签的做用与区别

  • :没有defer和async属性,加载到该标签时,浏览器会当即加载和执行相应的脚本,会阻塞后面的文档的加载,
  • :有了async属性以后,js脚本的加载与后续文档的加载和渲染是并行执行的,即异步执行。脚本加载以后当即执行,脚本执行时会阻塞 HTML 解析。
  • :有了defer属性,后续文档的加载与js脚本的加载(不包括执行)是并行执行的,即异步操做。js的执行会等到文档全部元素解析完成以后,DOMContentLoaded事件触发执行以前。
所以,defer与async的区别就是js加载以后什么时候执行:
它们在加载脚本都是异步执行
async在加载以后就会当即执行

若是存在多个有defer属性的脚本,那么它们是按照加载顺序执行脚本的

都只适用于外部脚本文件,对与内联的 script 标签是不起做用
复制代码

因此若是必定要将script放在头部,但又不想阻塞页面的加载,则加上defer属性

JS 识别不一样浏览器信息

function myBrowser() {
  var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串 
  var isOpera = userAgent.indexOf("Opera") > -1;
  if (isOpera) {
    return "Opera"
  }; //判断是否Opera浏览器 
  if (userAgent.indexOf("Firefox") > -1) {
    return "Firefox";
  }  //判断是否Firefox浏览器 
  if (userAgent.indexOf("Chrome") > -1) {
    return "Chrome";
  }   //判断是否Google浏览器 
  if (userAgent.indexOf("Safari") > -1) {
    return "Safari";
  } //判断是否Safari浏览器 
  if (userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1 && !isOpera) {
    return "IE";
  }; //判断是否IE浏览器 
} 
复制代码

HTML、XML与JSON

  • HTML是超文本标记语言,除了能够标记文本以外,标记 图片,视频,连接 等其余内容。
  • XML是可扩展性的标记语言,就是在文档里加上标签来讲明里面的数据是什么意思。方便存储、传输、分享数据。与HTML的区别是,HTML标签是预约义的,XML是可扩展的。
  • JSON是比较轻量级的数据交换格式,由键值对组成,方便读写,格式都是通过压缩的,占用带宽小。

编写一个方法,求一个字符串的字节长度

  • String.prototype.charAt(index):返回的是字符
  • String.prototype.charCodeAt(index):返回的是字符编码

假设:一个英文字符占用一个字节,一个中文字符占用两个字节

function getBytes(str){
    var len = str.length;   //获取字符串长度
    var bytes = len;
    for(var i = 0; i < len; i++){
        if (str.charCodeAt(i) > 255)  bytes++; //中文则+1
    }
    return bytes;
}
alert(getBytes("你好,as"));
复制代码

new操做符具体干了什么?

  • 建立一个新对象
  • 这个新对象的原型(Object.getPrototypeOf(target))指向构造函数的prototype对象
  • 该函数内的this会绑定在新建立的对象上
  • 若是函数没有返回其余对象,那么会自动返回这个新对象

原型

function f(){}
typeof f.prototype;  //"object"
typeof f.__proto__;  //"function"
f.__proto__===Function.prototype    //true
typeof Function.prototype;          //"function"
复制代码
  • 全部引用类型(对象、数组、函数)都有__proto__隐式属性
  • 因为兼容性问题,如今使用 Object.getPrototypeOf(target) 来替代 proto
  • 使用 let obj=Object.create(target),让obj.proto=target
  • Object.setPrototypeOf(target)(写操做)
  • prototypeObj.isPrototypeOf(object):测试一个对象是否存在于另外一个对象的原型链上
  • obj.hasOwnProperty(prop): 指示对象自身属性中是否具备指定的属性,拒绝查找原型链

有哪些性能优化的方法?

web 前端是应用服务器处理以前的部分,前端主要包括:HTML、CSS、javascript、image 等各类资源,针对不一样的资源有不一样的优化方式。

内容优化

  • 减小 HTTP 请求数。这条策略是最重要最有效的,由于一个完整的请求要通过 DNS 寻址,与服务器创建链接,发送数据,等待服务器响应,接收数据这样一个消耗时间成本和资源成本的复杂的过程。 常见方法:合并多个 CSS 文件和 js 文件,利用 CSS Sprites 整合图像,Inline Images (使用 data:URL scheme 在实际的页面嵌入图像数据 ),合理设置 HTTP 缓存等。
  • 减小 DNS 查找
  • 避免重定向
  • 使用 Ajax 缓存
  • 延迟加载组件,预加载组件
  • 减小页面上的DOM 元素数量。页面中存在大量 DOM 元素,会致使 javascript 遍历 DOM 的效率变慢。
  • 最小化 iframe 的数量。iframes 提供了一个简单的方式把一个网站的内容嵌入到另外一个网站中。但其建立速度比其余包括 JavaScript 和 CSS 的 DOM 元素的建立慢了 1-2 个数量级。
  • 避免 404。HTTP 请求时间消耗是很大的,所以使用 HTTP 请求来得到一个没有用处的响应(例如 404 没有找到页面)是彻底没有必要的,它只会下降用户体验而不会有一点好处。

服务器优化

  • 使用内容分发网络(CDN)。把网站内容分散到多个、处于不一样地域位置的服务器上能够加快下载速度。
  • GZIP 压缩
  • 设置 ETag:ETags(Entity tags,实体标签)是 web 服务器和浏览器用于判断浏览器缓存中的内容和服务器中的原始内容是否匹配的一种机制。
  • 提早刷新缓冲区
  • 对 Ajax 请求使用 GET 方法
  • 避免空的图像 src
  • Cookie 优化,减少 Cookie 大小针对 Web组件使用域名无关的 Cookie

CSS 优化

  • 将 CSS 代码放在 HTML 页面的顶部
  • 避免使用 CSS 表达式
  • 使用 < link> 来代替 @import
  • 避免使用 Filters

javascript 优化

  • 将 JavaScript 脚本放在页面的底部。
  • 将 JavaScript 和 CSS 做为外部文件来引用。 在实际应用中使用外部文件能够提升页面速度,由于 JavaScript 和 CSS 文件都能在浏览器中产生缓存。
  • 缩小 JavaScript 和 CSS
  • 删除重复的脚本
  • 最小化 DOM 的访问。使用 JavaScript 访问 DOM 元素比较慢。
  • javascript 代码注意:谨慎使用 with,避免使用 eval Function 函数,减小做用域链查找。

图像优化

  • 优化图片大小
  • 经过 CSS Sprites 优化图片
  • 不要在 HTML 中使用缩放图片
  • favicon.ico 要小并且可缓存

类数组转为数组

  • Array.prototype.slice.call(类数组 [,0])
  • Array.from(类数组)

应用:将节点列表 (NodeList) 转换为数组

若是你运行 document.querySelectorAll("p") 方法,它可能会返回一个 DOM 元素的数组 — 节点列表对象。 但这个对象并不具备数组的所有方法,如 sort(),reduce(), map(),filter()。 为了使用数组的那些方法,你须要把它转换为数组。

var elements = document.querySelectorAll("p"); // NodeList
var arrayElements = [].slice.call(elements); // 如今 NodeList 是一个数组

var arrayElements = Array.from(elements); // 这是另外一种转换 NodeList 到 Array 的方法
复制代码

jq 的 ready 和 onload 事件的区别

  • onload 是等 HTML 的全部资源都加载完成后再执行 onload 里面的内容,全部资源包括 DOM 结构、图片、视频 等资源;
  • ready 是当 DOM 结构加载完成后就能够执行了,至关于 jQuery 中的 $(function(){ js 代码 });
  • 另外,onload 只能有一个,ready 能够有多个。

Object.create(proto)函数

  • Object.create()方法建立一个新对象,传入的参数对象为建立的对象的__proto__
  • 在建立的对象上改变继承的属性不会对传入的参数对象有影响。
  • 返回建立的对象的constructor是ƒ Object()

例子

var a4 = { a: 1 }
var a3 = Object.create(a4);

console.log("a3.__proto__:", a3.__proto__); // Object {a: 1}

console.log(oo.constructor.prototype==Object.prototype)//true

console.log(a3.__proto__ === a3.constructor.prototype); //false

//通常obj.__proto__=obj.constructor.prototype,除了Object.create()建立的对象
复制代码

闭包

  • 什么是闭包:闭包是一个对象,里面保存着内部函数引用的外部函数的变量及变量的值
  • 闭包的做用:
    • 可以访问函数里的变量
    • 能让这些变量一直保存在内存中
function out(x){
       return function () {
           let ins='inside'
           console.log(x)
       }
}
let inside=out('xx');
   inside();
复制代码

打断点以后能看到Scopes里的closure对象就是闭包,里面保存着引用的变量

  • 造成闭包的条件:只要是嵌套的函数,内部函数引用了外部函数的变量就会造成闭包

堆和栈

  • 堆是没有结构的,数据能够任意存放。用于为引用类型的数据分配空间
  • 栈是有结构的,主要存放基本数据类型的变量和对象的引用。好比有:函数的参数、局部变量等。
  • 栈内存是线性的有序存储,容量小,系统分配效率高
  • 存储在堆内存的变量须要先在堆内存中分配存储区域,以后再将地址存储到栈内存中,效率就相对较低。/
  • 关于内存的回收:
    • 存储在栈内存的变量用完就回收了
    • 存储在堆内存的变量不能轻易回收,由于存在不肯定的引用,只有当引用的变量为0时才能回收。

如何实现跨域?

在JavaScript中,有一个很重要的安全性限制,被称为同源策略。这一策略对于JavaScript代码可以访问的页面内容作了很重要的限制,即JavaScript只能访问与包含它的文档在同一域下(协议+域名+端口号相同)的内容。

script、image、link都是能跨域的,是发送get请求。在页面上有三种资源是能够与页面自己不一样源的。它们是:js脚本,css样式文件,图片

  • jsonp:解决AJAX受同源策略的限制的问题,但jsonp只能发送get请求。原理是动态生成script标签,在src属性写上要不一样域的路径,而后将回调函数加在路径后面,做为参数传过去。
  • WebSocket:是一种双向通讯协议,创建链接后,客户端和服务端都能主动向对方发送或接收数据。
  • CORS:须要浏览器支持,整个过程由浏览器自动完成,不需用户参与,当发现请求跨域时,会自动添加一些附加的头信息。
  • 使用postMessage API(otherWindow.postMessage(message, targetOrigin, [transfer]);)
  • document.domain+iframe,只能解决主域相同的跨域问题。使用iframe去加载同一主域的页面,而后用js添加document.domain=该页面的域名或更高一级的父域,在请求的页面也需写上这一属性。
  • window.name+代理iframe。当iframe的页面跳转到其余网站时,其window.name值保持不变,可是主页面仍然是不可访问跨域下的iframe的window.name,因此须要一个和主页相同域的代理页面,这样当iframe从请求数据的跨域页面加载完后(监听onLoad事件),再切换成这个代理页面时,就可访问window.name而不会有跨域的问题
  • 代理服务器(Node中间件):服务器向服务器请求就无需遵循同源策略。客户端向本地的代理服务器发送请求,代理服务器接收请求,将请求转发给目标服务器,最后将目标服务器的响应结果转发给枯客户端。
  • nginx反向代理:实现原理相似于Node中间件代理,须要你搭建一个中转nginx服务器,用于转发请求。(须要下载nginx)

总结:

  • CORS支持全部类型的HTTP请求,是跨域HTTP请求的根本解决方案
  • JSONP只支持GET请求,JSONP的优点在于支持老式浏览器,以及能够向不支持CORS的网站请求数据。
  • 无论是Node中间件代理仍是nginx反向代理,主要是经过同源策略对服务器不加限制。
  • 平常工做中,用得比较多的跨域方案是cors和nginx反向代理

例子1 例子2

CORS

浏览器将CORS请求分为两类:简单请求和非简单请求,对于非简单请求,在正式通讯以前,增长一次HTTP查询请求,称为“预检”请求。

简单请求:
一、get、post、head
二、HTTP头信息不超过如下几种字段:Accept、Accept-Language、Content-Language、Content-Type(application/x-www-form-urlencoded 或 multipart/form-data 或 text/plain)
复制代码
非简单请求:
对服务器有特殊要求的请求,好比put、delete,或者Content-Type类型是application/json
复制代码

资料

ES6

资料

  • 使用export导出数据
  • 使用import接收
util.js文件
//一、先定义后导出
let num1=1; let num2=2;
export {num1,num2};
//二、直接导出定义的变量
export var num=1;
export var num2=2;

main.js
//接收
import {num1,num2} from 'util.js'
//能够起别名
import {num1:n1,num2:n2} from 'util.js'
//能够一次性全接收
import * as obj from 'util.js';
console.log(obj.num1,obj.num2);
---------------------------

util.js文件
//三、导出默认数据
export default const obj={}

main.js
//接收
import data from 'util.js'; //能够随意命名一个变量
-------------------------------

//同时导出默认的和部分的,export default只能使用一次
export default const example3 = {
  birthday : '2018 09 20'
}
export let name  = 'my name'
export let age  = 'my age'
export let getName  = function(){ return 'my name'}

// 导入默认与部分
import example3, {name, age} from './example1.js'

复制代码

迭代器与生成器

  • 迭代器:迭代器是一个对象,含有next方法,该方法返回一个对象,该对象有done和value属性,value表示迭代到的值,done表示是否迭代结束,当数据迭代完毕,返回true

用ES5实现迭代器的建立

function generator(arr) {
        let i=0;
        return {
            next(){
                let done=(i>=arr.length);
                let value=!done? arr[i++]:undefined
                return{done,value}
            }
        }
    }
    let iterator=generator([1,2,3]);    //获得迭代器对象
    let obj=iterator.next();
    while (!obj.done) {
        console.log(obj.value);
        obj=iterator.next();
    }
复制代码
  • 生成器:ES6语法,在函数名前加一个*表示生成器,用来生成一个迭代器对象,函数像正常代码同样编写,函数内部使用yield关键字来指定每次迭代到的数据,当运行到yield函数就会中止向下执行,直到迭代器调用next方法

用ES6的生成器建立迭代器,迭代数组

function *Generator(arr) {
        for(let i=0;i<arr.length;i++){
            yield arr[i]
        }
    }
    let it=Generator([1,2,3]);
    let obj_=it.next();
    while (!obj_.done){
        console.log(obj_.value);
        obj_=it.next()
    }
复制代码

可迭代对象有:数组、字符串、map、set对象,这些都是含有Symbol.iterator属性

commonjs模块(require和module.exports)和ES6模块(import和export)的区别

  • commonjs是动态加载,是运行时执行,require能够写在任何地方。获取到的是模块的浅拷贝,能够对模块从新赋值
  • es6是静态加载,是编译时加载,import必须写在文件头部,获取到的模块的引用,因此对模块只有只读,不能修改模块指向的地址,对模块从新赋值会编译报错

es6模块

export const person = {
    name: 'lc',
    friends: ['xm','xh']
};
setTimeout(() => person.name ='liangcheng', 500);
复制代码

commonjs模块

var dog = {
    name: 'xsz', // 不要猜想是谁的名字
    owner: ['lc']
};
module.exports = dog;

setTimeout(() => dog.name = 'xushizhou' ,500);
复制代码

引用模块

import { person } from './a1';
const dog = require('./a2');

setTimeout(() => console.log(person.name, dog.name), 1000); //liangcheng xushizhou
复制代码

面向对象

三要素:封装、继承、多态

# 1.封装
// 假设须要登记学籍,分别记录小明和小红的学籍号、姓名
let name1 = "小明"
let num1 = "030578001"
let name2 = "小红"
let num2 = "030578002"

// 若是须要登记大量的数据,则弊端会很是明显,并且很差维护,那么咱们会使用如下方法来登陆,这也是面向对象的特性之一:封装

let p1 = {
    name:"小明",
    num:"030578001"
}

let p2 = {
    name:"小红",
    num:"030578002"
}
# 2.继承
// 从已有的对象上,获取属性、方法
function Person(){
    this.name = "邵威儒"
}

Person.prototype.eat = function(){
    console.log("吃饭")
}

let p1 = new Person()
p1.eat() // 吃饭

let p2 = new Person()
p2.eat() // 吃饭

# 3.多态
// 同一操做,针对不一样对象,会有不一样的结果
let arr = [1,2,3]
arr.toString() // 1,2,3

let obj = new Object()
obj.toString() // [object Object]

复制代码

面向过程和面向对象的本质理解

  • 面向过程是流程化的,分析解决问题须要几个步骤,用函数把每个步骤实现,在使用的时候调用函数便可。
  • 面向对象是模型化的,抽象出一个类,这是一个封闭的环境,在这个环境中有数据有解决问题的方法,你若是须要什么功能直接使用就能够了,至因而怎么实现的,你不用知道。

总结: 面向对象的底层仍是面向过程,面向过程抽象成类,而后封装,方便使用就是面向对象。

ES5的继承和ES6的继承的区别

  • ES5的继承实质上是先建立子类的实例,再将父类的方法添加到this上 (Parent.apply(this))
  • ES6是先建立父类的实例对象this(经过调用super方法来调用父类的构造函数),而后子类再将本身的属性和方法添加到this上
class Parent{
        constructor(){
            this.x='parent'
        }
    }
class Son extends Parent{
    constructor(){
    var s=super()
    console.log(s === this);    //true
    }
}
var son=new Son()
复制代码

图片懒加载

  • 将页面上的<image>的src属性写成data-src,将图片的路径写在这个下面
  • 一开始加载页面就判断图片距离浏览器的顶部的距离是否小于窗口可视高度($img.getBoundingClientRect().top<=window.innerHeight; ),是则将data-src属性赋给src属性
  • 而后将已加载的图片设一个标记位,表示已加载过,下次无需再判断
  • 而后监听页面的滑动,要使用防抖函数,防止持续的滚动页面从而不断的触发相关函数致使性能损耗。
  • 滑动事件监听函数里也是进行前三步的判断

资料

用CSS3动画替代JS模拟动画的好处

好处:

  • 不占用js主线程
  • 能够采用硬件加速(将浏览器的渲染过程交给GPU处理,可使得animation和transition更加顺畅)
    • 使用3d效果来开启硬件加速:transform3d/rotate3d/scale3d/translateZ
  • 浏览器能够对动画进行优化(元素不可见时不动画,减小对FPS的影响)

坏处:

  • 浏览器对渲染的批量异步化处理会致使动画难以控制,须要强制同步
$.fn.repaint = function () {
   this.each(function (item) {
     return item.clientHeight;
 });  
}
复制代码

CSS3动画与javascript模拟动画有如下区别:

  • CSS 3D动画在js中没法实现
  • CSS 2D矩阵动画(指矩阵transform的变化,好比scale、变形、x轴、y轴)效率高于js用margin和left、top模拟的矩阵动画
  • CSS3其它常规动画属性的效率均低于js模拟的动画,常规动画属性在这里是指:height,width,opacity,border-width,color…..

资料

JavaScript设计模式

资料

Webpack

  • 做用:使前端实现模块化开发,可以使用require与exports,export与import

  • 优势:

    • 依赖管理:方便引用第三方模块、让模块更容易复用、避免全局注入致使的冲突、避免重复加载或加载不须要的模块。
    • 各路插件:babel 把 ES6+ 转译成 ES5 ,eslint 能够检查编译期的错误……
    • 合并代码:把各个分散的模块集中打包成大文件,减小 HTTP 的请求连接数,配合 UglifyJS 能够减小、优化代码的体积。
  • 原理:一切皆为模块,因为 webpack 并不支持除 .js 之外的文件,从而须要使用 loader 转换成 webpack 支持的模块,plugin插件用于扩展 webpack 的功能,在 webpack 构建生命周期的过程在合适的时机作了合适的事情。

  • webpack 从构建到输出文件结果的过程

    • 解析配置参数,合并从 shell 传入和 webpack.config.js文件的配置信息,输出最终的配置信息
    • 注册配置中的插件,好让插件监听 webpack 构建生命周期中的事件节点,作出对应的反应
    • 解析配置文件中 entry 入口文件,并找出每一个文件依赖的文件,递归下去
    • 在递归每一个文件的过程当中,根据文件类型和配置文件中 loader 找出相对应的 loader 对文件进行转换
    • 递归结束以后获得每一个文件最终的结果,根据 entry 配置生成代码 chunk
    • 输出全部 chunk 到文件系统

模块化和组件化

  • 组件化从功能出发,强调的是功能性和可复用性
  • 模块化从业务逻辑出发,强调的是完整性和业务性
  • 好处是:提升代码的复用性,解耦

Vue

数据劫持与监听

vue.js是经过Object.defineProperty以及发布订阅模式来进行数据劫持和监听

v-model的原理

v-model实际是个语法糖,原理是用v-bind绑定表单组件的value/selected/checked,和v-on绑定input/change事件。一般用在表单控件的双向绑定。

自定义组件的v-model如何生效

对于添加了v-model指令的组件(若是含表单控件input),会默认利用value和input事件。但若是是其余类型的表单控件checkbox,就会利用checked和change事件,因此为了通用性,组件将会有model对象属性,定义了prop和event属性来定义绑定的值和事件类型

  • 在组件添加v-model指令
<base-checkbox v-model="lovingVue"></base-checkbox>
复制代码
  • 至关于写成
<base-checkbox v-bind:checked="lovingVue" v-on:change="lovingVue=arguments[0]"></base-checkbox>
复制代码
  • 这个组件:
Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
      //checkbox表单控件本身有change事件,它的事件监听函数触发的是这个vue组件的change事件,是父组件绑定的事件,两个change事件是属于不一样对象的
    >
  `
})
复制代码

综上:父组件的lovingVue传递给子组件,子组件用checked接收,注意在子组件要在props属性显示指出checked这个prop。当子组件的表单控件发生变化,触发父组件绑定的事件,将表单控件的值做为事件函数的参数传递过去,从而改变父组件的lovingVue值。实现子组件更新父组件的值。

资料

computed和watch的区别

相同:二者都是能监听/依赖一个数据,并进行相应的处理

  • computed:相似于过滤器,对绑定到视图的数据进行处理

    • 在computed里定义函数,函数被看成属性使用,函数要有返回值
    • 具备缓存,只有依赖的属性发生变化时才会从新计算求值
    • 不能够执行异步操做
  • watch:watch是一个侦听的动做,用来观察和响应 Vue 实例上的数据变更

    • 是以绑定到视图的数据命名的函数(底层实际上是里面包裹了一个名为handler的函数)
    • 当数据发送变化时,这个响应函数就会被调用
    • 这个函数有两个参数,newVal和oldVal
    • 能够执行异步操做
    • 因为能够执行异步操做,因此在获得最终结果前能够设置中间状态
<div id="watch-example">
  <p>
    Ask a yes/no question:
    <input v-model="question">
  </p>
  <p>{{ answer }}</p>
</div>
复制代码

watch执行异步操做,限制咱们执行该操做的频率,并在咱们获得最终结果前,设置中间状态。这些都是计算属性没法作到的:

<!-- 由于 AJAX 库和通用工具的生态已经至关丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可让你自由选择本身更熟悉的工具。 --> <script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script> <script> var watchExampleVM = new Vue({ el: '#watch-example', data: { question: '', answer: 'I cannot give you an answer until you ask a question!' }, watch: { // 若是 `question` 发生改变,这个函数就会运行 question: function (newQuestion, oldQuestion) { this.answer = 'Waiting for you to stop typing...' this.debouncedGetAnswer() } }, created: function () { // `_.debounce` 是一个经过 Lodash 限制操做频率的函数。 // 在这个例子中,咱们但愿限制访问 yesno.wtf/api 的频率 // AJAX 请求直到用户输入完毕才会发出。想要了解更多关于 // `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识, // 请参考:https://lodash.com/docs#debounce this.debouncedGetAnswer = _.debounce(this.getAnswer, 500) }, methods: { getAnswer: function () { if (this.question.indexOf('?') === -1) { this.answer = 'Questions usually contain a question mark. ;-)' return } this.answer = 'Thinking...' var vm = this axios.get('https://yesno.wtf/api') .then(function (response) { vm.answer = _.capitalize(response.data.answer) }) .catch(function (error) { vm.answer = 'Error! Could not reach the API. ' + error }) } } }) </script> 复制代码

总结:computed主要用于对同步数据的处理,watch则主要用于观测某个值的变化去完成一段开销较大的复杂业务逻辑。

Vue的单向数据流

  • 全部的prop使得父子组件间造成单向下行绑定:父级prop的更新会向下流动到子组件中,所以子组件的prop时时能获取到父组件更新的值。
  • 但子组件不容许改变prop,若是想改变能够经过$emit派发一个自定义事件,父组件接收后,由父组件修改。

有两种常见的试图改变一个prop的情形:

  • 将prop做为子组件的初始值,而且这个子组件接下来但愿将其做为一个本地的prop数据使用:最好定义到本地的data属性
props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}
复制代码
  • 这个 prop 以一种原始的值传入且须要进行转换:最好使用这个 prop 的值来定义一个计算属性
props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}
复制代码

以前给数组项赋值和给一个空对象添加属性,Vue能检测到变化吗?

不能。

  • 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  • 当你修改数组的长度时,例如:vm.items.length = newLength
虽然Object.defineProperty()能够监听到数组下标的变化,
可是为了性能优化,vue源码并无使用Object.defineProperty()来劫持数组类型的数据:
复制代码

如何解决?

  • 使用Vue.set(数据,属性/索引,属性值)
  • 使用vue.$set(数据,属性/索引,属性值)
  • 改变数组长度:vm.items.splice(newLength)

虚拟DOM树-- Virtual DOM 算法

  • 有js对象结构表示DOM树的结构,而后利用它构建一个真正的DOM树,再插入到文档中。
  • 当状态变动时,用js对象结构再建立一个虚拟的DOM,而后与以前的虚拟DOM进行比较,记下差别的地方
  • 把差别应用到真正的DOM树上,视图就更新了

难点:比较两棵虚拟DOM树的差别

比较两棵 DOM 树的差别是 Virtual DOM 算法最核心的部分,这也是所谓的 Virtual DOM 的 diff 算法。

  • 两个树的彻底的 diff 算法是一个时间复杂度为 O(n^3) 的问题。可是在前端当中,你不多会跨越层级地移动 DOM 元素。

因此 Virtual DOM 只会对同一个层级的元素进行对比:

上面的 div 只会和同一层级的 div 对比,第二层级的只会跟第二层级对比。这样算法复杂度就能够达到 O(n)。

深度优先遍历,记录差别

在实际的代码中,会对新旧两棵树进行一个深度优先的遍历,这样每一个节点都会有一个惟一的标记:

在深度优先遍历的时候,每遍历到一个节点就把该节点和新的的树进行对比。若是有差别的话就记录到一个对象里面。

Vue生命周期

  • new一个Vue实例时,实例只有默认的事件和生命周期函数

  • 最先能访问到this是:beforeCreate函数

  • 在beforeCreate函数以后created以前:data、methods、watch、computed属性进行初始化

  • 在created函数能访问data、methods、watch、computed属性,除了el属性

  • 在created函数以后,beforeMount函数以前:进行模板编译

    • 若是没有el属性,则等到调用vm.$mounted()挂载时再继续往下执行
    • 若是没有template属性,则将el的outerHtml内容做为模板template
    • 将template编译成render函数
  • beforeMounte函数:此时内存已构建出虚拟的dom树,视图还未更新,不能访问dom上一些动态变量,此时访问el还是未各类表达式{{}}

  • beforeMounte函数以后:建立vm.$el,与el进行替换

  • Mounted函数:已将虚拟dom挂载到页面上,能访问dom上的动态变量


  • 此时已完成实例的初始化阶段,进入运行阶段
  • beforeUpdate函数:data已经发生变化触发该函数,视图还未更新
  • beforeUpdate函数以后:根据data从新渲染新的虚拟dom,对比新老虚拟dom树,将差别应用到真正的dom树上,完成页面更新
  • updated函数:此时页面已经是最新的数据

  • beforeDestory函数:实例销毁前调用,this仍能获取到实例,经常使用于销毁定时器、解绑全局事件、销毁插件对象等操做
  • destroyed函数:在Vue 实例销毁后调用。调用后,Vue 实例指示的全部东西都会解绑定,全部的事件监听器会被移除,全部的子实例也会被销毁。

)

Vue.use()

  • 在全局注册/安装组件,把组件挂载到Vue.prototype上,全部组件实例均可访问

组件间传递数据

  • props(父向子,当传递的不是变量仅是单纯的字符串时,不须要冒号绑定)
  • 父组件在子组件标签上绑定事件 v-on,子组件触发事件$emit(子向父)
  • slot:父向子传递‘标签数据’
//父组件
<child>
    <div slot="xxx">父组件动态插入数据</div>
    <div slot="yyy">父组件动态插入数据</div>
</child>   

//子组件
<template>
    <div>
        <slot name='xxx'></slot>
        <div>组件肯定的标签结构</div>
        <slot name='yyy'></slot>
    </div>
</template>
复制代码
  • $refs.名称,获取组件实例
  • $parent / $children:访问父 / 子实例
// component-a 子组件
export default {
  data () {
    return {
      title: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
    }
  }
}
复制代码
// 父组件
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // Vue.js
      comA.sayHello();  // 弹窗
    }
  }
</script>
复制代码
  • bus
    • 建立一个js文件,使用export default向外暴露一个对象,对象含有install方法
    • 在入口文件main.js引入这个文件,赋给一个变量,好比bus
    • 使Vue.use(bus),这个方法会去调用bus的install方法,且会传入Vue做为参数
    • 在bus对应的js文件的install函数写:将$bus属性添加到Vue.prototype上,属性值是一个Vue实例(由于为了实现数据更新,视图更新,因此让$bus=Vue实例,利用Vue的data有数据劫持的特色。)
    • 将共用的数据添加在data对象(是对象不是方法,由于不是组件,是js文件不是.vue文件)
    • 在methods添加on方法、emit方法、off方法
//event-bus.js文件
export default const install=function(Vue){
    Vue.prototype.$bus=new Vue({
        methods:{
            emit(event,...args){
                this.$emit(event,...args);
            },
            on(event,callback){
                this.$on(event,callback);
            },
            off(event,callback){
                this.$off(event,callback)
            }
        }
    })
}
复制代码

在入口文件main.js引入插件

import bus from 'event-bus.js'
Vue.use(bus)
复制代码

使用$bus绑定事件this.$bus.on()要注意:

一、绑定在哪一个生命周期函数,才能确保在其余组件触发事件以前已经绑定好事件,即组件间生命周期函数的执行顺序

二、在组件销毁以前将事件解除,不然当再有组件绑定该事件,添加事件监听函数时,此时事件的监听函数还保留着以前的事件监听函数,即会触发多个回调。

  • vuex

    • 安装Vuex
    • 新建一个js文件,引入Vuex,使用Vue.use()安装Vuex
    • 向外暴露一个Vuex.Store实例
    • 向Vuex.Store()构造函数传入四个对象:state、getters、actions(定义的方法能够重名,会按注册的顺序依次触发。能够包含同步/异步操做,用来提交mutation,不直接改变状态state)、mutations(定义的方法不能够重名,只能进行同步操做)
    • 在main.js引入这个文件,在Vue实例添加store属性等于这个文件暴露出来的Vuex.Store实例,这样全部组件都有$store属性
    • 使用$store.state访问数据
    • 使用$store.dispatch(action名,data)调用actions里的方法
    • actions里的方法使用commit调用mutations
  • pubsub消息发布/订阅

    • 安装pubsub-js
    • 接收数据的组件订阅消息
    • 传递数据的组件发布消息
//订阅消息
mounted: {
PubSub.subscribe("deleteTodo",(messageName, todosIndex)=>{
    this.deleteTodo(todosIndex);
});
//发布消息
methods: {
PubSub.publish("deleteTodo", this.index);
}
复制代码
  • $attrs/$listeners:

多级组件嵌套须要传递数据时,一般使用的方法是经过vuex。但若是仅仅是传递数据,而不作中间处理,使用 vuex 处理,未免有点大材小用。

$attrs:包含了从父组件接收的(来自父做用域的),但props没有定义的数据 (class 和 style 除外)。 当一个组件没有声明任何 prop 时,$attrs这里会包含全部父做用域的绑定 (class 和 style 除外),而且能够经过 v-bind="$attrs" 传入内部组件 这是惟一一次v-bind直接与=相连,通常要传给子组件且props能接收的要v-bind:变量名=数据

$listeners:包含了父做用域中的 (不含 .native 修饰器的) v-on 事件监听器。它能够经过 v-on="$listeners" 传入内部组件


总结:

  • 父向子传递数据:props,$attrs/$listeners、slot(传递的是标签数据)

  • 子向父传递数据:事件绑定和触发$emit事件回调,$ref获取组件实例

  • 兄弟间:bus,vuex,pubsub订阅发布消息

  • 跨级通讯:bus,vuex,pubsub订阅发布消息,$attrs/$listeners

  • 任意级别通讯:bus、vuex、pubsub订阅发布消息

Vuex的mapState、mapGetters、mapActions

一、state是什么?

  • vuex的state和vue中的data有不少类似之处,都是用于存储一些数据或者说是状态值。
  • 这些值都将被挂载到数据和dom的双向绑定事件,则当你改变值的时候能够触发dom的更新。
  • 虽然state和data有不少类似之处,但state在使用时通常应该挂载到组件的computed计算属性上,这样有利于state的值发生改变时及时响应给子组件(若是你用data去接收$store.state,固然能够接收到值,但因为这只是一个简单的赋值操做,所以state中的状态改变的时候不能被vue中的data监听到,固然你也能够经过watch $store去解决这个问题)

二、mapState辅助函数 mapState是语法糖,不用在computed里屡次定义函数,而后返回不一样的this.$store.state.数据名,能够直接将store的state直接添加到computed里

  • 使用前,引入这个辅助函数
  • 语法:mapState( { } / [ ] ),传入对象或数组
import { mapState } from 'vuex'
...
computed: mapState({
    count: 'count', // 第一种写法,count为state里定义的,后面的count能够省略
    sex: (state) => state.sex, // 第二种写法
    from: function (state) { // 用普通函数this指向vue实例,要注意
      return this.str + ':' + state.from
    },
    // 注意下面的写法看起来和上面相同,事实上箭头函数的this指针并无指向vue实例,所以不要滥用箭头函数
    // from: (state) => this.str + ':' + state.from
    
    //也能够在mapState里定义不使用到state的函数
    myCmpted: function () {
      // 这里不须要state,测试一下computed的原有用法
      return '测试' + this.str
    }
})
复制代码

三、...mapState

  • ...mapState是ES6语法,展开符
  • 当computed已经定义一些函数时,此时发现要引入state,就可使用展开符将state添加到computed里
computed:{
    //原来的继续保留
    fn1(){ return ...},
    fn2(){ return ...},
    fn3(){ return ...}
    ......
    //再维护vuex
    ...mapState( //这里的...不是省略号了,是对象扩展符
       [ count ]
    )
}
复制代码

自定义指令

语法:

  • 全局指令:
Vue.directive('指令名',{
    bind(el,binding,vnode){
        //一开始绑定时调用,只调用一次
        //el:绑定指令的dom元素,binding传递的是绑定的值,存储的是对象
    },
    update(el,binding,vnode){
        //绑定的数据发生更新时调用,0-屡次
    }
})
复制代码
<p v-focus:top="10"></p>

Vue.directive('focus',{
    bind(el,binding,vnode){
        el.style[binding[arg]]=binding[value]+'px'
    },
    update(el,binding,vnode){
        //绑定的数据发生更新时调用,0-屡次
    }
})
复制代码
  • 私有指令:在Vue实例里定义directives属性
directives:{
    focus:{
        binding(el,binding){
            
        }
    }
}
复制代码

v-for添加key属性

当Vue用 v-for 正在更新已渲染过的元素列表是,它默认用“就地复用”策略。若是数据项的顺序被改变,Vue将不是移动DOM元素来匹配数据项的改变,而是简单复用此处每一个元素,而且确保它在特定索引下显示已被渲染过的每一个元素。

  • 由于vue组件高度复用组件,增长Key能够标识组件的惟一性,更好地区别各个组件
  • key的做用主要是为了高效的更新虚拟DOM diff算法

$router与$route的区别

  • $router是VueRouter的一个实例,是经过安装并引入VueRouter,安装VueRouter —— Vue.use(VueRouter)和new VueRouter({})获得的,是一个全局对象
    • $router.push({path:'/home'}):使用js方式代替切换路由,能够回退到上一个
    • $router.replace({path:'/home'}):替换路由,没有历史记录
  • $route是局部对象,指定当前路由,$route有path(绝对路径)、params、query、name等属性

vue-router会出现的相关问题

一、当使用路由参数切换路由,实现同一个路由路径+不一样的参数来展现不一样的内容,即:先后都是同一组件,但显示的内容不同

  • 缘由:原来的组件会被复用,由于两个路由都渲染同一组件,比起销毁再建立,复用则显得更加高效,这意味着组件的生命周期函数不会再调用
  • 解决:在当前组件监视路由的变化
watch:{
    $route(to,from){
        //to:就是切换后的路由
        //根据需求更改数据
    }
}
复制代码

二、使用路由切换组件时,一切换以前的组件就会被销毁,再次切换到该组件时,以前的状态不会保存,由于是再次建立的

  • 解决:使用<keep-alive>包裹,实现组件缓存。可是要根据需求决定是否缓存组件。

vue-router的导航守卫

vue-router 提供的导航守卫主要用来经过跳转或取消的方式守卫导航。 有多种机会植入路由导航过程当中:全局的, 单个路由独享的, 或者组件级的。

展现一种全局的,更多的查看连接:

路由的元信息

定义路由的时候能够配置 meta 字段,属性值是对象

meta:能够与上面的导航守卫搭配使用,好比切换到网站的主页路由时,须要用户登陆才可成功跳转,显示组件,那就在该路由加配置meta字段,当跳转路由时,就会触发router.beforeEach钩子函数,在里面进行对路由的meta里自定义的一些标志位的判断,再决定是否放行。

SPA单页面

SPA:仅在Web页面初始化时加载相应的HTML、JavaScript、CSS,加载完成后,不会由于用户的操做而进行页面的从新加载或跳转。取而代之的是利用路由机制来实现HTML内容的变换,避免页面的从新加载。

  • 优势:

    • 用户体验好、快:内容改变时不用从新加载整个页面,避免了没必要要的跳转和重复渲染。
    • 基于上面一点,SPA对服务器压力小
  • 缺点:

    • 初次加载耗时多,得等到Vue编译后的JS文件下载完后才能渲染
    • SEO难度大:因为全部内容都在一个页面中动态地替换显示

单页应用的一些基本配置

Vue SSR

  • Vue.js是构建客户端应用程序的框架。默认状况下,能够在浏览器中输出 Vue 组件,进行生成 DOM 和操做 DOM。
  • SSR是指在服务端将组件渲染成html片断而后返回给浏览器,最后将这些静态标记"激活"为客户端上彻底可交互的应用程序。

服务端渲染SSR的优缺点:

  • 优势:
    • 更好的SEO:由于 SPA 页面的内容是经过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,因此在 SPA 中是抓取不到页面经过 Ajax获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),因此搜索引擎爬取工具能够抓取渲染好的页面。
    • 首屏加载速度更快: SPA 会等待全部 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等须要必定的时间等,因此首屏渲染须要必定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,因此 SSR 有更快的内容到达时间;
  • 缺点:
    • 更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数;服务端渲染应用程序,须要处于 Node.js server 运行环境;
    • 更多的服务器负载

MVVM

  • view层:视图层,由HTML和css构建
  • model层:数据模型,保存每一个页面中单独的数据
  • viewModel层:视图模型层,是v与m的桥梁,起承上启下的做用。
    • 向上于视图层进行双向数据绑定,向下与模型层经过接口请求的方式进行数据交互。
    • vm将获取到的数据进行处理转换,作二次封装,以生成符合view层的数据模型。这样view层展示的不是model层的数据,而是vm层的数据。
    • 双向数据绑定使前端开发者没必要低效又麻烦地去操做dom去更新视图。

MVVM和MVC的区别

  • MVC:是后端分层开发的概念。V层接收用户的输入操做,C层响应view的事件,当涉及到数据的增删改查曾调用model层的接口对数据进行操做,model层数据变化后通知v层更新视图。
  • MVVM:与MVC最大的区别就是实现了m层与v层的自动同步,vm层是m层和v层的桥梁,与v层进行双向数据绑定,与m层经过接口的形式进行数据交换。开发者不用手动操做dom就能实现v层的更新。(在vm层直接修改数据便可实现v层的更新)

Vue如何实现数据的双向绑定

  • view -- > data变化:经过事件监听的方式
  • data --> view变化:数据劫持与监听、订阅发布模式
    • Object.defineProperty()为data对象的每层属性添加getter和setter,当数据发生改变,就会触发setter,从而监听到数据的变化。实现数据的劫持与监听(Observer)
    • 当在遍历每一层属性时,在每一层都实例化一个Dep实例--订阅器,用于存放订阅者,当某个属性发生变化,该层的Dep实例就会发布消息,让Dep实例上的全部订阅者去更新视图。
    • 订阅者的来历:先编译模板(Compile),将指令替换成数据显示,每个指令都实例出一个Watcher实例(订阅者),Watcher实例含更新视图的函数,在Watcher的构造函数里,会去访问data的属性,从而调用getter,所以在getter里找到该层属性的Dep实例,将Watcher实例添加到Dep实例里,实现了订阅。
    • Watcher是Dep和Compile的桥梁
    • Dep是Observer和Watcher的桥梁

vue为何不能监听数组的变化

在vue中,不能检测到如下对数组的操做:

  • arr[index]=newVal
  • arr.length=5

缘由?

  • vue是经过Object.defineProperty实现数据劫持的,给属性添加getter和setter
  • 但Object.defineProperty也能将数组的索引当初属性来添加getter和setter
  • 但出于对性能的考虑,vue的源码只对是对象类型的数据使用Object.defineProperty

vue只能监听如下八种对数组操做的方法:

push()
pop()
shift()
unshift()
splice()
sort()
reverse()
复制代码

Object.defineProperty() 和 proxy 的区别

vue中为了对对象的每层属性都实现监听,须要不断遍历对象的属性,使用Object.defineProperty()来实现数据劫持,则Object.defineProperty()只能劫持数据的属性,若是能直接劫持一个完整的对象而非对象上的属性,则无需层层遍历属性了。

Proxy:

Proxy 能够理解成,在目标对象以前架设一层“拦截”,外界对该对象的访问,都必须先经过这层拦截,所以提供了一种机制,能够对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操做,能够译为“代理器”。

var proxy=new Proxy(target,handler)

参数:

  • target:是用Proxy包装的被代理对象(能够是任何类型的对象,包括原生数组,函数,甚至另外一个代理)
  • handler:一个对象,定义了一些函数,用于拦截对应的操做。好比,定义一个get方法,用来拦截对目标对象属性的访问请求。

返回值:

  • 返回代理对象,直接操做该对象来达到操做目标对象的效果
  • 若是直接操做目标对象不能达到Proxy的效果
let obj = {};
 let handler = {
   get(target, property) {
    console.log(`${property} 被读取`);
    return property in target ? target[property] : 3;
   },
   set(target, property, value) {
    console.log(`${property} 被设置为 ${value}`);
    target[property] = value;
   }
 }
 
 let p = new Proxy(obj, handler);
 p.name = 'tom' //name 被设置为 tom
 p.age; //age 被读取 3
复制代码

p 读取属性的值时,实际上执行的是 handler.get() :在控制台输出信息,而且读取被代理对象 obj 的属性。

p 设置属性值时,实际上执行的是 handler.set() :在控制台输出信息,而且设置被代理对象 obj 的属性的值。

handler能够定义多种拦截的函数:Proxy


综上:取代Object.defineProperty()的Proxy有如下两个优势

能直接劫持整个对象,无需经过递归和遍历data对象来实现对数据的监控
有多种劫持操做
复制代码

Webpack

模块化解决了前端的哪些痛点

  • 代码复用
  • 命名冲突
  • 文件依赖

webpack 的 loader 和 plugin 区别

  • loader用于对模块中的源代码进行转换。loader 能够将文件从不一样的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至容许你直接在 JavaScript 模块中 import CSS文件! 由于 webpack 自己只能处理 JavaScript,若是要处理其余类型的文件,就须要使用 loader 进行转换,loader 自己就是一个函数,接受源文件为参数,返回转换的结果。
  • Plugin 是用来扩展 Webpack 功能的。使用 plugin 丰富的自定义 API 以及生命周期事件,能够控制 webpack 打包流程的每一个环节,实现对 webpack 的自定义功能扩展。plugin能监听webpack构建生命周期的事件节点。
相关文章
相关标签/搜索