JavaScript

原始类型与援用类型的区别

原始类型(值类型,根本类型):数值(Number),字符串(String),布尔(Boolean),nullundefined
援用类型:对象(Object)
区别:

  1. 赋值:原始类型赋(值),援用类型赋(援用)
// 原始类型赋值(真的赋值,内存空间1)let str1 = 'hello'let str2 = str1str1 = 'world'console.log(str1) //worldconsole.log(str2) //hello        //援用类型的赋值(赋援用,在内存空间1申明,在内存空间2赋值)let stu1 = {name: 'xm'}let stu2 = stu1stu1.name = {name: 'xh'}console.log(stu1.name)  //xhconsole.log(stu2.name)  //xh
  1. 比拟:原始类型比拟的是值是否相等,援用类型比拟的是援用是否指向对立对象
// 原始类型的比拟let str1 = 'hello'let str2 = 'hello'console.log(str1 === str2)//true//援用类型的比拟let stu1 = {name: 'xm'}let stu2 = {name: 'xm'} console.log(stu1 === stu2)//falselet stu2 = stu1console.log(stu1===stu2)//true
  1. 函数传参:原始类型作为参数,函数内的操作不影响实参的值。援用类型作为参数,函数内的操作会影响实参的值。
//原始类型传参let fn1 = function(num) {    num = 100}let n = 10fn1(n)console.log(n)      //10//援用类型传参let fn2 = function(arr) {    arr.push(10)}let a = [1, 2, 3]fn2(a)console.log(a)      //[1,2,3,10]

typeof 和 instanceof

typeof:

  • 是一元运算符,用于判断数据类型,返回值为字符串
  • 别离为:string、Boolean、number、function、object、undefined、symbol
  • typeof在判断null、array、object及函数实例(new+函数)时,失去的时object。这使得在判断这些数据类型的时候,得不到实在的数据类型。由此引出instanceof
    instanceof:
  • instance中文翻译为实例,因而含意显而易见,判读该对象是谁的实例,同时咱们也就晓得instanceof是对象运算符。
  • instanceof运算符用来测试一个对象在其原型链中是否存在一个构造函数的prototype属性。用于判断一个变量是否某个对象的实例
var a = new Array();alert(a instanceof Array);//truealert(a instanceof Object);//true,因为Array是object的子类

why?

  • 每个函数function都有一个prototype,即原型。每个对象都有一个proto,为隐式原型。
  • 每个对象都有一个proto属性,指向创立该对象的prototype。

      function Foo(){};  var f1 =new Foo();  alert(f1 instanceof Foo) //true

instanceof 判断规定

  • instanceof运算符的第一个变量是一个对象,暂称为A,第二个变量个别是函数,称为B。
  • 沿着A的proto这条线来找,同时沿着B的prototype这条线来找
  • 如果两条线能找到同一个援用,就是返回true,如果找到起点都没有重合就返回false

new的作用

  • js中的new是来创立实例对象的。
  • new开拓了一个新的空间来存储构造函数中初始化的数据,并将地址作为返回值返回
  • 如果没有new,构造函数中的this指向全局变量,没有返回值,会显示undefined

实现的步骤:

  1. new会在内存中创立一个新的空对象
  2. new会让this指向这个新的对象
  3. 执行构造函数外面的代码

    • 目标:给这个新对象加属性和办法
  4. new会返回这个新对象(所以构造函数外面不须要return)

防抖与节流

防抖:

  • 用户触发事件过于频繁,只有最初一次事件的操作
  • 避免反复调用,进步性能,防止卡死。
    <body>        <input type="text">    </body>    <script>        //模仿发送后盾申请,此时在input框内输出任何文本,后盾即时更新        let inp = document.querySelector("input");        inp.oninput = function() {                console.log(this.value)            }            //应用计时器增加防抖性能,此时在input框内输出文本在0.5内不会触发,只有超过0.5s才会触发,这就实现了简略的防抖性能        let inp = document.querySelector("input");        inp.oninput = debounce(function() {            console.log(this.value)        }, 500)        function debounce(fn, delay) {            let t = null;            return function() {                if (t !== null) {                    clearTimeout(t);                }                t = setTimeout(() => {                    fn.call(this) //调用call转换指针到input事件                }, delay)            }        }    </script>

节流:

  • 管制执行次数
  • 作用:管制高频事件执行次数
    <style>        body {            height: 2000px //用高高度呈现滚动条来模仿频繁操作        }    </style>    <script>        window.onscroll = throttle(function() {            console.log('hello world') //调用throttle不论触发事件多疾速,都只在0.5s内执行一次        }, 500)        function throttle(fn, delay) {            let flag = true            return function() {                if (flag) {                    setTimeout(() => {                        fn.call(this) //调用call转换指针到onscroll事件                        flag = true                    }, delay)                }                flag = false            }        }    </script>

this的各种状况

  1. 以函数模式调用时,this永远都是window
  2. 以办法的模式调用时,this是调用办法的对象
  3. 以构造函数的模式调用时,this是新创建的那个对象
  4. 应用call和apply调用时,this时指定的那个对象
  5. 箭头函数:箭头函数的this看外层是否有函数,

    • 如果有,外层函数的this就是外部箭头函数的this。
    • 如果没有就是window
  6. 非凡状况:通常意义上this指针指向为最初调用它的对象,然而须要留神一点的就是:

    • 如果返回值是一个对象,那么this指向的就是那个返回的对象
    • 如果返回值不是一个对象,那么this还是指向函数的实例

call apply bind

  • call是一个办法,是函数的办法
  • call能够调用函数
    function fun(){        console.log('hello world')    }    fun.call()
  • call能够扭转函数中this的指向
    function fun(){        console.log(this.name)    }    let dog={        name:'bob',        sayName(){            console.log('我是'+this.name)        },        eat(food1,food2){            console.log('我喜爱吃'+food1+food2)        }    }    let cat = {name:'tom'}    fun.call(cat)//tom    dog.sayName()//bob    dog.sayName.call(cat)//tom    dog.eat('骨头')    dog.eat.call(cat,'鱼')//call第一个参数是扭转this的指向,前面的参数是要传的参数
  • apply

      dog.eat.apply(cat,['鱼'])````### == 和 === 的区别,什么时候用==?**==:**运算符称作相等,用来检测两个操作数是否相等,这里的相等定义十分宽松,能够进行类型转换,比方              console.log(123 == '123') //返回true
  • 对于stringnumber等根底类型,== 和 ===是有区别的。

    • ==是比拟‘转化成对立类型后的值看此值是否相等,就是上述输入为何为true的起因
    • ===如果类型不同,那么后果就是不等
  • 同类型比拟,间接进行 值 比拟,两者后果一样。
  • 对于ArrayObject等高级类型,== 和 ===是没有去别的
  • 根底类型与高级类型,== 和 === 是有区别的

    • ==,将高级转化为根底类型,进行值比拟,
    • 因为类型不同,=== 后果为false

    js中垃圾回收机制是什么,罕用的是哪种,怎么解决的?

    js的垃圾回收机制是为了以防内存透露,内存透露的含意就是当曾经不须要的某块内存是,这块内存还存在着,垃圾回收机制就是间歇的不定期的寻找到不再应用的变量,并开释掉它们所指向的内存
    js中最常见的垃圾回收形式 是标记革除
    工作原理:是当变量进入环境时,将这个变量标记为“进入环境”,当变量来到环境时,则将其标记为“来到环境”。标记“来到环境”的就回收内存
    工作流程:

  • 垃圾回收期,在运行的时候会给存储在内存中的所有变量都加上标记。
  • 去掉环境中的变量以及被环境中的变量援用的变量的标记
  • 在被加上标记的会被视为筹备删除的变量
  • 垃圾回收器实现内存革除工作,销毁那些带标记的值并回收他们所占用的内存空间

    for in 和 for of的区别

  • for...in次要是为了遍历对象而生,不适用于遍历数组
  • for...of 能够用来遍历数组,类型组对象,字符串,Set、Map及Generator对象
  • 遍历输入不同

    • for...in:只能取得对象的键名,不能取得键值
    • for...of:容许遍历取得键值
      var arr = ['red', 'green', 'blue']  //for in  for (let item in arr) {      console.log(item)//0 1 2  }  //for of  for (let item of arr) {      console.log(item)//red green blue  }
  • 对于一般对象,没有部署原生的iterator接口,间接应用for...of会报错

       var obj = {       'name':'BOB',       'age':'22'       }       //for in      for (let key in arr) {             console.log(key)//name age      }      //for of      for (let key of arr) {             console.log(key)//Uncaught  TypeError: obj is not iterable              }
  • 能够应用Object.keys(obj)办法将对象的键名生成一个数组,而后遍历这个数组

           for(let key of Object.keys(obj)){                   console.log(key)//name age               }
  • for...in不仅遍历数字键名,还会遍历手动增加的其余键,甚至包含原型链上的键。for...of则不会这样

       let arr = [7,8,9]       arr.set = 'world'//手动增加的键       Array.prototype = 'hello'//原型链上的键       for(let item in arr){           console.log(item)//0 1 2 set name       }       for(let item of arr){           console.log(item)//7 8 9        }
  • forEach 无奈中途跳出,break命令或return命令都不能见效

       let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]       arr.forEach(i => {           if (i % 2 === 0) {               return           }           console.log(i)//1 3 5 7 9       })
  • for...of能够与break,continue和return配合应用,跳出循环

      for(let i of arr){       if(i % 2 === 0){        break      }       console.log(i)//1}
  • 无论是for...in还是for...of都不能遍历出Symbol类型的值,遍历Symbol类型的值须要用Object.getOwnPropertySymbols() 办法

      let a = Symbol('a')          let b = Symbol('a')
        [a]: 'hello',        [b]: 'world',            c: 'es6',            d: 'dom'        }        for (let key in obj) {            console.log(key + '的值是' + obj[key])        }        let objSymbols = Object.getOwnPropertySymbols(obj)        console.log(objSymbols)        objSymbols.forEach(i => {            console.log(i, obj[i])        })        let keyArray = Reflect.ownKeys(obj)        console.log(keyArray)

设计模式

前端常见的设计模式次要有以下几种:

  1. 单例模式

这种设计模式的思维是确保一个类只有惟一实例,个别用于全局缓存,比方全局window,惟一登录浮窗等。

  1. 工厂模式

工厂模式是创建对象的罕用设计模式,为了不裸露创建对象的具体逻辑,将逻辑封装在一个函数中,这个函数就称为一个工厂。实质上是一个负责生产对象实例的工厂。工厂模式依据形象水平的不同能够分为:简略工厂,工厂办法和形象工厂。

  1. 策略模式

策略模式的本意将算法的应用与算法的实现拆散开来,防止多重判断调用哪些算法。实用于有多个判断分支的场景,如解决表单验证的问题。你能够创立一个validator对象,有一个validate()办法。这个办法被调用时不必辨别具体的表单类型,它总是会返回同样的后果——一个没有通过验证的列表和错误信息

  1. 代理模式

代理模式是为其余对象提供一种代理,也就是当其余对象间接拜访该对象时,如果开销较大,就能够通过这个代理层管制对该对象的拜访。常见的应用场景为懒加载,合并http申请和缓存

  1. 观察者模式

也叫公布订阅模式,在这种模式中,一个订阅者订阅发布者,当一个特定的事件产生的时候,发布者会告诉(调用)所有的订阅者。

  1. 模块模式

模块模式能够指定类想裸露的属性和办法,并且不会净化全局

  1. 构造函数模式
  2. 混合模式

构造函数和混合模式就是js中继承的两种实现形式,前者通过构造函数的模式定义类,通过new新增实例。而后者是将构造函数的援用属性和办法放到其原型上,子类是父类原型的一个实例。

前端模块化

阐明:
模块化开发是一种治理形式,是一种生产方式,一种解决问题的计划,一个模块就是实现特定性能的文件,有了模块,咱们就能够更不便地应用他人的代码,想要什么性能,就加载什么模块,然而模块化开发须要遵循肯定的标准,否则就都乱套了,因而,才有了起初大家相熟的AMD标准,CMD标准,以及ES6自带的模块化标准。
模块化带来的益处:

  • 解决命名抵触
  • 提供复用性
  • 进步代码可维护性
  • 灵便架构,焦点拆散,不便模块间组合,合成
  • 多人合作互不烦扰

留神:

  • CommonJS标准次要用于服务端编程,加载模块是同步的,这并不适宜在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的,因而有了AMD,CMD解决方案
  • AMD标准在浏览器环境中异步加载模块,而且能够并行加载多个模块。不过,AMD标准开发成本高,代码的浏览和书写比拟艰难,模块定义形式的语义不顺畅
  • CMD标准和AMD标准很类似,都用于浏览器编程,依赖就近,提早执行,能够很容易在Node.js中运行。不过,依赖SPM打包,模块的加载逻辑并重
  • ES6在语言规范的层面上,实现了模块性能, 而且实现的相当简略,齐全能够取代CommonJS和AMD标准,成为浏览器和服务器通用的模块解决方案。

前端工程化

前端工程化是指应用软件工程的技术和办法来进行前端的开发流程,技术,工具,教训等规范化,标准化,其次要目标是为了提高效率和降低成本,既进步开发过程中的开发效率,缩小不必要的反复工作工夫。
前端工程化里的工程指软件工程,和咱们个别说的工程是两个齐全不同的概念

  • 工程是个很泛泛的概念,甚至能够认为建了一个git仓库就相当于建了一个工程
  • 软件工程的定义:利用计算机科学实践和技术以及工程治理准则和办法,按估算和进度,实现满足用户要求的软件产品的定义,开发,和保护的工程或进行钻研的学科

前端工程化就是为了让前端开发可能“自成体系”,集体认为次要从模块化,组件化,规范化,自动化思考

  • 模块化

    • 简略说模块化就是将一个大文件拆分成相互依赖的小文件,再进行对立的拼装和加载。
  • JS的模块化
    在ES6之前,JavaScript始终没有模块零碎,这对开发大型简单的前端工程造成了微小的阻碍。对此社区制订了一些模块加载计划,如CommonJS、AMD和CMD等。
    当初ES6曾经在语言层面上规定了模块零碎,齐全能够取代现有的CommonJS和AMD标准,而且应用起来相当简洁,并且有动态加载的个性。

    • 用++Webpack + Babel++将所有模块打包成一个文件同步加载,也能够搭乘多个chunk异步加载
    • 用++System+Babel++次要是分模块异步加载
    • 用浏览器的<script type="module">加载
  • css的模块化

尽管SASS、LESS、Stylus等预处理器实现了CSS的文件拆分,但没有解决CSS模块化的一个重要问题:选择器的全局净化问题。

按情理,一个模块化的文件应该要暗藏外部作用域,只裸露大量接口给使用者。而依照目前预处理器的形式,导入一个CSS模块后,已存在的款式有被笼罩的危险。尽管重写款式是CSS的一个劣势,但这并不利于多人合作。

为了防止全局选择器的抵触,须要制订CSS命名格调:BEM格调,Bootstrap格调

从工具层面,社区又发明出Shadow DOM、CSS in JS和CSS Modules三种解决方案。

  • Shadow DOM是WebComponents的规范。它能解决全局净化问题,但目前很多浏览器不兼容,对咱们来说还很长远
  • CSS in JS是彻底摈弃CSS,应用JS或JSON来写款式。这种办法很激进,不能利用现有的CSS技术,而且解决伪类等问题比拟艰难
  • CSS Modules依然应用CSS,只是让JS来治理依赖。它可能最大化地联合CSS生态和JS模块化能力,目前来看是最好的解决方案。Vue的scoped style也算是一种
  • 资源的模块化

Webpack的弱小之处不仅仅在于它对立了JS的各种模块零碎,取代了Browserify、RequireJS、SeaJS的工作。更重要的是它的万能模块加载理念,即所有的资源都能够且也应该模块化。

资源模块化后,长处是:

  • 依赖关系单一化。所有CSS和图片等资源的依赖关系对立走JS路线,无需额定解决CSS预处理器的依赖关系,也不需解决代码迁徙时的图片合并、字体图片等门路问题;
  • 资源解决集成化。当初能够用loader对各种资源做各种事件,比方简单的vue-loader等等
  • 我的项目构造清晰化。应用Webpack后,你的我的项目构造总能够示意成这样的函数: dest = webpack(src, config)。
  • 组件化

从UI拆分下来的每个蕴含模板(HTML)+款式(CSS)+逻辑(JS)性能齐备的结构单元,咱们称之为组件。
组件化≠模块化。模块化只是在文件层面上,对代码或资源的拆分;而组件化是在设计层面上,对UI(用户界面)的拆分。
页面上所有的货色都是组件。页面是个大型组件,能够拆成若干个中型组件,而后中型组件还能够再拆,拆成若干个小型组件,小型组件也能够再拆,直到拆成DOM元素为止。DOM元素能够看成是浏览器本身的组件,作为组件的根本单元。

传统前端框架/类库的思维是先组织DOM,而后把某些可复用的逻辑封装成组件来操作DOM,是DOM优先;而组件化框架/类库的思维是先来构思组件,而后用DOM这种根本单元联合相应逻辑来实现组件,是组件优先。这是两者实质的区别。

其次,组件化实际上是一种依照模板(HTML)+款式(CSS)+逻辑(JS)三位一体的模式对面向对象的进一步形象。

所以咱们除了封装组件自身,还要正当解决组件之间的关系,比方 (逻辑)继承、(款式)扩大、(模板)嵌套和蕴含等,这些关系都能够归为依赖。

目前市面上的组件化框架很多,次要的有Vue、React、Angular。Vue文档中的比照其余框架一文曾经讲得很具体了。

  • 规范化

规范化其实是工程化中很重要的一个局部,我的项目初期标准制订的好坏会间接影响到前期的开发品质。
比方:

  • 目录构造的制订
  • 编码标准

    • HTML标准
    • CSS标准
    • JS标准
    • 图片标准
    • 命名标准
  • 前后端接口标准
  • 自动化

    • 图标合并
    • 继续集成
    • 自动化构建
    • 自动化部署
    • 自动化测试

script阻塞dom

浏览器解析html文件时,从上向下解析构建DOM树。当解析到script标签时,会暂停DOM构建。先把脚本加载并执行结束,才会持续向下解析。js脚本的存在会阻塞DOM解析,进而影响页面渲染速度。

  • 解决方案

    • 将script标签放在html文件底部,防止解析DOM时被其阻塞
    • 提早脚本(在script标签上设置defer属性,告知浏览器立刻下载脚本,但提早执行。当浏览器解析完html文档时,再执行脚本。<script type="text/javascript" defer="defer" src="1.js"></script>)
    • 异步脚本(和defer性能相似,区别在于不会严格依照script标签程序执行脚本,也就是说脚本2可能先于脚本1执行。脚本都会在onload事件前执行,但可能会在 DOMContentLoaded 事件触发前后执行。 <script type="text/javascript" async src="1.js"></script>)
    • 留神:defer和async都只实用于内部脚本 

webpack

webpack是一个打包模块化js的工具,能够通过loader转换文件,通过plugin扩大性能。
外围概念

  • entry:一个可执行模块或者库的入口。
  • chunk:多个文件组成一个代码块。能够将可执行的模块和他所依赖的模块组合成一个chunk,这是打包。(打包文件生成的一大堆代码,实际上就是一个自执行函数,仅传入一个参数为 modules,且该对象为一个数组,该函数的作用就是治理模块,它的外部定义了两个次要的对象 installedModules 对象(被当作字典应用,key 是模块的 id,value 是代表模块状态和导出的一个对象)和 __webpack_require__(moduleId) 函数对象。
  • loader:文件转换器。例如把es6转为es5,scss转为css等
  • plugin:扩大webpack性能的插件。在webpack构建的生命周期节点上退出扩大hook,增加性能。
  • 构建流程

    • Webpack 的运行流程是一个串行的过程,从启动到完结会顺次执行以下流程:
    • 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
    • 开始编译:用上一步失去的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 办法开始执行编译
    • 确定入口:依据配置中的 entry 找出所有的入口文件;
    • 编译模块:从入口文件登程,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都通过了本步骤的解决;
    • 实现模块编译:在通过第4步应用 Loader 翻译完所有模块后,失去了每个模块被翻译后的最终内容以及它们之间的依赖关系;
    • 输入资源:依据入口和模块之间的依赖关系,组装成一个个蕴含多个模块的 Chunk,再把每个 Chunk 转换成一个独自的文件退出到输入列表,这步是能够批改输入内容的最初机会;
    • 输入实现:在确定好输入内容后,依据配置确定输入的门路和文件名,把文件内容写入到文件系统。
    • 在以上过程中,Webpack 会在特定的工夫点播送出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件能够调用 Webpack 提供的 API 扭转 Webpack 的运行后果。
  • webpack的热更新是如何做到的?阐明其原理

    • webpack的热更新又称热替换(Hot Module Replacement),缩写为HMR。 这个机制能够做到不必刷新浏览器而将新变更的模块替换掉旧的模块。

      • 第一步,在 webpack 的 watch 模式下,文件系统中某一个文件产生批改,webpack 监听到文件变动,依据配置文件对模块从新编译打包,并将打包后的代码通过简略的 JavaScript 对象保留在内存中
      • 第二步是 webpack-dev-server 和 webpack 之间的接口交互,而在这一步,次要是 dev-server 的中间件 webpack-dev-middleware 和 webpack 之间的交互,webpack-dev-middleware 调用 webpack 裸露的 API对代码变动进行监控,并且通知 webpack,将代码打包到内存中
      • 第三步是 webpack-dev-server 对文件变动的一个监控,这一步不同于第一步,并不是监控代码变动从新打包。当咱们在配置文件中配置了devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中动态文件的变动,变动后会告诉浏览器端对利用进行 live reload。留神,这儿是浏览器刷新,和 HMR 是两个概念
      • 第四步也是 webpack-dev-server 代码的工作,该步骤次要是通过 sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间建设一个 websocket 长连贯,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包含第三步中 Server 监听动态文件变动的信息。浏览器端依据这些 socket 音讯进行不同的操作。当然服务端传递的最次要信息还是新模块的 hash 值,前面的步骤依据这一 hash 值来进行模块热替换
      • webpack-dev-server/client 端并不可能申请更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack,webpack/hot/dev-server 的工作就是依据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有前面那些步骤了
      • HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接管到上一步传递给他的新模块的 hash 值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 申请,服务端返回一个 json,该 json 蕴含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 申请,获取到最新的模块代码
      • 而第 10 步是决定 HMR 胜利与否的关键步骤,在该步骤中,HotModulePlugin 将会对新旧模块进行比照,决定是否更新模块,在决定更新模块后,查看模块之间的依赖关系,更新模块的同时更新模块间的依赖援用
      • 最初一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码
  • 如何利用webpack来优化前端性能

    • 压缩代码。删除多余的代码、正文、简化代码的写法等等形式。能够利用webpack的UglifyJsPlugin和ParallelUglifyPlugin来压缩JS文件, 利用cssnano(css-loader?minimize)来压缩css。应用webpack4,打包我的项目应用production模式,会主动开启代码压缩。
    • 利用CDN减速。在构建过程中,将援用的动态资源门路批改为CDN上对应的门路。能够利用webpack对于output参数和各loader的publicPath参数来批改资源门路
    • 删除死代码(Tree Shaking)。将代码中永远不会走到的片段删除掉。能够通过在启动webpack时追加参数--optimize-minimize来实现或者应用es6模块开启删除死代码。
    • 优化图片,对于小图能够应用 base64 的形式写入文件中
    • 依照路由拆分代码,实现按需加载,提取公共代码。
    • 给打包进去的文件名增加哈希,实现浏览器缓存文件
  • 如何进步webpack的构建速度

    • 多入口的状况下,应用commonsChunkPlugin来提取公共代码;
    • 通过externals配置来提取罕用库;
    • 应用happypack实现多线程减速编译
    • 应用webpack-uglify-parallel来晋升uglifyPlugin的压缩速度。原理上webpack-uglify-parallel采纳多核并行压缩来晋升压缩速度;
    • 应用tree-shaking和scope hoisting来剔除多余代码

      Webpack是什么? Webpack与Grunt、Gulp有什么不同?

  • 首先咱们先答复这样的问题,这三者没什么可比性的。
  • grunt和gulp在晚期比拟风行,属于前端工具类,次要优化前端工作流程。比方主动刷新页面、combo、压缩css、js、css预编译等等
  • 当初webpack相对来说比拟支流,属于预编译模块的计划,不须要在浏览器中加载解释器。另外,你在本地间接写JS,不论是 AMD / CMD / ES6 格调的模块化,它都能意识,并且编译成浏览器意识的JS
  • grunt和gulp是基于工作和流(Task、Stream)的。相似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作形成了一个工作,多个工作就形成了整个web的构建流程
  • webpack是基于入口的。会主动地递归解析入口所须要加载的所有资源文件,而后用不同的Loader来解决不同的文件,用Plugin来扩大webpack性能
  • 从构建思路来说,gulp和grunt须要开发者将整个前端构建过程拆分成多个 Task,并正当管制所有 Task的调用关系 webpack须要开发者找到入口,并须要分明对于不同的资源应该应用什么Loader做何种解析和加工
  • 总结

    • gulp,grunt是web构建工具
    • webpack是模块化计划
    • gulp,grunt是基于工作和流
    • webpack基于入口文件

执行上下文

  • 执行上下文(Execution Context): 函数执行前进行的筹备工作(也称执行上下文环境)
  • 运行JavaScript代码时,当代码执行进入一个环境时,就会为该环境创立一个执行上下文,它会在你运行代码前做一些筹备工作,如确定作用域,创立局部变量对象等。

分类:

  • 全局执行上下文
  • 函数执行上下文
  • eval函数执行上下文(不举荐应用)

剖析:

  • JavaScript运行时首先会进入全局环境,对应会生成全局上下文。程序代码中根本都会存在函数,那么调用函数,就会进入函数执行环境,对应就会生成该函数的执行上下文。
  • 因为JS是"单线程"! "单线程"! "单线程"! 就是同个时间段只能做一件工作,实现之后才能够持续下一个工作。
  • 函数编程中,代码中会申明多个函数,对应的执行上下文也会存在多个。在JavaScript中,通过栈的存取形式来治理执行上下文,咱们可称其为执行栈,或函数调用栈(Call Stack)。
  • 执行栈,或函数调用栈(Call Stack)。
  • 程序执行进入一个执行环境时,它的执行上下文就会被创立,并被推入执行栈中(入栈);程序执行实现时,它的执行上下文就会被销毁,并从栈顶被推出(出栈),控制权交由下一个执行上下文。
  • 因为JS执行中最先进入全局环境,所以处于"栈底的永远是全局环境的执行上下文"。而处于"栈顶的是以后正在执行函数的执行上下文",当函数调用实现后,它就会从栈顶被推出(现实的状况下,闭包会阻止该操作,闭包后续文章深刻详解)。
  • "全局环境只有一个,对应的全局执行上下文也只有一个,只有当页面被敞开之后它才会从执行栈中被推出,否则始终存在于栈底。"

函数的 原型(prototype)

  1. 任何一个js对象,都有一个原型对象,它能够应用本人原型对象上的所有属性和办法

             let cat = {             name:'tom'         }         cat.__proto__.eat=function(){             console.log('吃鱼')         }         cat.eat()//吃鱼
  2. 通过构造函数prototype属性拿到原型

             function Cat(name,age){             this.name=name             this.age=age         }         let cat = new Cat('tom',2)         Cat.prototype.eat=function(){             console.log('吃鱼')         }         cat.eat()
  3. 原型对象的作用

能够给任何一个对象,通过原型来增加办法

显式原型和隐式原型

  1. 每个函数function都有一个prototype,即显式原型(属性)
  2. 每个实例对象都有一个__proto__ ,可称为隐式原型(属性)
  3. 对象的隐式原型的值为其对应构造函数的显式原型的值
  4. 内存构造
  5. 总结

    • 函数的prototype属性:在定义函数时主动增加的,默认值是一个空Object对象
    • 对象的__proto__属性:创建对象时主动增加的,默认值为构造函数的prototype属性值
    • 程序员能间接操作显式原型, 但不能间接操作隐式原型(ES6之前)
   //定义构造函数       function Fn(){    //外部语句:this.prototype = {}       }       //1.每个函数function都有一个prototype,即显式原型,默认指向一个空的Object对象       console.log(Fn.prototype)       //2.每个实例对象都有一个__proto__  ,可称为隐式原型       var fn = new Fn()    //外部语句:this.__proto__=Fn.prototype       console.log(fn.__proto__)       //3.对象的隐式原型的值为其对应构造函数的显式原型的值       console.log(Fn.prototype===fn.__proto__)     //true       //给原型增加办法       Fn.prototype.test = function(){         console.log('test')       }       //通过实例调用原型的办法       fn.test()

原型链 及 属性问题

  1. 原型链(图解)

    • 拜访一个对象的属性时,

      • 先在本身属性中查找,找到返回
      • 如果没有,再沿着__proto__这条链向上查找,找到返回
      • 如果最终没找到,返回undefined
    • 别名:隐式原型链
    • 作用:查找对象的属性(办法)
  2. 构造函数/原型/实体对象的关系(图解)
  3. 构造函数/原型/实体对象的关系(图解)
   function Fn(){       this.test1=function(){           console.log('test1()')       }   }   Fn.prototype.test2=function(){       console.log('test2()')   }                       var fn = new Fun()   fn.test1()   fn.test2()   console.log(fn.toString())   fn.test3()
  1. 函数的显示原型指向的对象:默认是空的Object实例对象(但Object不满足)
    console.log(Fn.prototype instanceof Object)        //true    console.log(Object.prototype instanceof Object)        //false    console.log(Function.prototype instanceof Object)    //true
  1. 所有函数都是Function的实例(蕴含Function)
   console.log(Function.__proto__===Function.prototype)    //true
  1. Objec的原型对象是原型链止境
    console.log(Object.prototype.__proto__)        //null

属性问题

  1. 读取对象的属性值时:会主动到原型链中查找
  2. 设置对象的属性值时:不会查找原型链,如果以后对象中没有此属性,间接增加此属性并设置其值
  3. 办法个别定义在原型中,属性个别通过构造函数定义在对象自身上
     function Fn(){     }     Fn.prototype.a = 'xxx'     var fn1 = new Fn()     console.log(fn1.a)                         var fn2 = new Fn()     fn2.a = 'yyy'     console.log(fn1.a)

属性放本身,办法放原型

    function Person(name,age){        this.name = name        this.age = age    }    Person.prototype.setName = function(name){        this.name = name    }    var p1 = new Person('Tom',12)    p1.setName('Bob')    console.log(p1)                                //Bob 12    var p2 = new Person('jack',12)    p2.setName('Cat')    console.log(p2)                                //Cat 12    console.log(p1.__proto__===p2.__proto__)       //true

作用域与作用域链

  1. 了解

    • 就是一块“地盘”,一个码段所在的区域
    • 它是动态的(绝对于上下文对象),在编写代码时就确定了
  2. 分类

    • 全局作用域
    • 函数作用域
    • 没有块作用域(ES6有了)
  3. 作用

    • 隔离变量,不同作用域下同名变量不会有抵触
    //没块作用域    if (true) {        var c = 3    }    console.log(c)                var a = 10,    b=2    function fn(x) {        var a = 100,        c = 300;    console.log('fn()',a,b,c,x)    function bar(x) {    var a = 1000,    d=400    console.log('bar',a,b,c,d,x)    }                bar(100)    bar(200)    }    fn(10)

作用域与执行上下文区别

区别1

  • 全局作用域之外,每个函数都会创立本人的作用域,作用域在函数定义时就曾经确定了。而不是在函数调用时
  • 全局执行上下文环境是在全局作用域确定之后,js代码马上执行之前创立
  • 函数执行上下文是在函数调用时,函数体代码执行之前创立

区别二

  • 作用域是动态的,只有函数定义好了就始终存在,且不会再变动
  • 执行上下文是动静的,调用函数时创立,函数调用完结时就会主动开释

分割

  • 上下文环境(对象)是从属于所在的作用域
  • 全局上下文环境==>全局作用域
  • 函数上下文环境==>对应的函数作用域

作用域链

  1. 了解

    • 多个上下级关系的作用域造成的链,它的方向时从下向上的(从内到外)
    • 查找变量时就是沿着作用域链来查找的
  2. 查找一个变量的查找规定

    • 在以后作用域下的执行上下文中查找对应的属性,如果有间接返回,否则进入2
    • 在上一级作用域的执行上下文中查找对应的属性,如果有间接返回,否则进入3
    • 再次执行2的雷同操作,晓得全局作用域,如果还找不到就抛出找不到的异样
    var a = 1    function fn1(){        var b = 2        function fn2(){            var c = 3            console.log(c)            console.log(b)            console.log(a)            console.log(d)        }        fn2()    }    fn1()

闭包的了解

什么是闭包:

  • 各种业余文献的闭包定义都十分形象,我的了解是: 闭包就是可能读取其余函数外部变量的函数。
  • 因为在javascript中,只有函数外部的子函数能力读取局部变量,所以说,闭包能够简略了解成“定义在一个函数外部的函数“。
  • 所以,在实质上,闭包是将函数外部和函数内部连接起来的桥梁。
    闭包的用途:
  • 闭包能够用在许多中央。它的最大用途有两个,
  • 一个是后面提到的能够读取函数外部的变量,
  • 另一个就是让这些变量的值始终保持在内存中,不会在f1调用后被主动革除。
    注意事项:
  • 因为闭包会使得函数中的变量都被保留在内存中,内存耗费很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决办法是,在退出函数之前,将不应用的局部变量全副删除。
  • 闭包会在父函数内部,扭转父函数外部变量的值。所以,如果你把父函数当作对象(object)应用,把闭包当作它的专用办法(Public Method),把外部变量当作它的公有属性(private value),这时肯定要小心,不要轻易扭转父函数外部变量的值。

promise

  • ES6中一个十分好用和重要的个性
  • Promise是异步编程的一种计划
  • 什么时候用?

    • 个别状况下是有异步操作时,应用Promise对这个异步操作进行封装
    • Promise需传入俩个参数,resolve(胜利时调用),reject(失败时调用)
      new Promise((resolve, reject) => {          setTimeout(() => {              // 胜利的时候调用resolve              // resolve('hello world')              // 失败的时候调用reject                    reject('error message')                }, 1000)            }).then((data) => {       // 1.100行解决的代码       console.log(data);       console.log(data);       console.log(data);       console.log(data);       console.log(data);       console.log(data);            }).catch((err) => {       console.log(err)            })    //输入 error message

Promise三种状态

  • 当开发中有异步操作时,就能够给异步操作包装一个Promise
  • 异步操作之后会有三种状态

    • pending:期待状态,比方正在进行网络申请,或者定时器没有到工夫
    • fulfill:满足状态,当咱们被动回调了resolve时,就处于该状态,并且会回调.then()
    • reject:回绝状态,当咱们被动回调了reject时,就处于该状态,并且会回调.catch()
    new Promise((resolve,reject)=>{        setTimeout(()=>{            resolve('hlwd')            reject('error')        },1000)    }).then(data => {        console.log(data);    },err => {        console.log(err)    })

Promise的链式调用

        new Promise((resolve, reject) => {            // 第一次网络申请的代码            setTimeout(() => {                resolve()            }, 1000)        }).then(() => {            // 第一次拿到后果的解决代码            console.log('hellowd')            console.log('hellowd')            console.log('hellowd')            console.log('hellowd')            console.log('hellowd')            console.log('hellowd')            return new Promise((resolve, reject) => {                // 第二次网络申请的代码                setTimeout(() => {                    resolve()                }, 1000)            })        }).then(() => {            // 第二次拿到后果的代码            console.log('hellow')            console.log('hellow')            console.log('hellow')            console.log('hellow')            console.log('hellow')            console.log('hellow')            return new Promise((resolve, reject) => {                // 第三次网络申请的代码                setTimeout(() => {                    resolve()                })            },1000)        }).then(() => {            // 第三次拿到后果的代码            console.log('hellowd')            console.log('hello')            console.log('hello')            console.log('hello')            console.log('hello')            console.log('hello')        })
  • 简化代码形式:
        // 网络申请:aaa -> 本人解决(十行)        // 解决:aaa111 -> 本人解决(十行)        // 解决:aaa111222 -> 本人解决        new Promise((resolve, reject) => {            setTimeout(() => {                resolve('aaa')            }, 1000)        }).then((res) => {            // 1.本人解决10行代码            console.log(res, '第一次的十行解决代码');            //2.对后果进行第一次解决            return new Promise((resolve) => {                resolve(res + '111')            })        }).then((res) => {            console.log(res, '第二次的十行解决代码');                    return new Promise((resolve) => {                resolve(res + '222')            })        }).then((res) => {            console.log(res, '第三次的10行解决代码');        })                    // SIMPLE WRITTEN         new Promise((resolve, reject) => {            setTimeout(() => {                resolve('aaa')            }, 1000)        }).then((res) => {            // 1.本人解决10行代码            console.log(res, '第一次的十行解决代码');                    //2.对后果进行第一次解决            return Promise.resolve(res + '111')                }).then((res) => {            console.log(res, '第二次的十行解决代码');                    return Promise.resolve(res + '222')        }).then((res) => {            console.log(res, '第三次的10行解决代码');        })              // SIMPLE WRITTEN 2        new Promise((resolve, reject) => {            setTimeout(() => {                resolve('aaa')            }, 1000)        }).then((res) => {            // 1.本人解决10行代码            console.log(res, '第一次的十行解决代码');                    //2.对后果进行第一次解决            return res + '111'            }).then((res) => {            console.log(res, '第二次的十行解决代码');                    return res + '222'        }).then((res) => {            console.log(res, '第三次的10行解决代码');        })              //报错        new Promise((resolve, reject) => {            setTimeout(() => {                resolve('aaa')            }, 1000)        }).then((res) => {            // 1.本人解决10行代码            console.log(res, '第一次的十行解决代码');                    //2.对后果进行第一次解决            return Promise.reject('报错')            //or            //throw '报错'        }).then((res) => {            console.log(res, '第二次的十行解决代码');                return Promise.resolve(res + '222')        }).then((res) => {            console.log(res, '第三次的10行解决代码');        }).catch((eerr) => {            console.log(eerr);        })

Promise的all办法

  • 退出某一需要要发送两次申请能力实现
            Promise.all([            new Promise((resolve, reject) => {                setTimeout(() => {                    resolve('result')                }, 2000)            }),            new Promise((resolve, reject) => {                setTimeout(() => {                    resolve('result2')                }, 1000)            })        ]).then(results => {            console.log(results);        })

深拷贝 与 浅拷贝

  • 深拷贝只是从源数据中拷贝一份进去进行操作,而不是扭转源数据,扭转源数据的是浅拷贝
  • 原生js办法slice,concat都不是真正意义上的深拷贝,都仅只实用于一维数组,拷贝的属性不够彻底
  • 实现js深拷贝能够通过JSON.parse(JSON.stringify()),递归实现

拷贝顾名思义就是复制,内存中分为栈内存和堆内存两大区域,所谓深浅拷贝次要是对js援用类型数据进行拷贝一份,浅拷贝就是援用类型数据互相赋值之后,例obj1=obj2;如果前面批改obj1或者obj2,这个时候数据是会进行相应的变动的,因为在内存中援用类型数据是存储在堆内存中,堆内存中寄存的是援用类型的值,同时会有一个指针地址指向栈内存,两个援用类型数据地址一样,如果其中一个发生变化另外一个都会有影响,而深拷贝则不会,深拷贝是会在堆内存中从新开拓一块空间进行寄存

根本类型复制:

                var a = 1                var b = a//复制                console.log(b)//1                a = 2//扭转a的值                console.log(b)//1                console.log(a)//2

a,b都是属于根本类型,根本类型的复制时不会影响对方的,因为根本类型时每一次创立变量都会在栈内存中开拓一块内存,用来寄存值,所以根本类型进行复制是不会对另外一个变量有影响的

js浅拷贝:

                var arr1 = ['red','green']                var arr2 = arr1//复制                console.log(arr2)//['red','green']                arr1.push('black')//扭转color值                console.log(arr2)//['red','green','blcak']                console.log(arr1)//['red','green','black']

此例子是数组的浅拷贝,通过下面的常识咱们能够看到数组是援用类型数据,援用类型数据复制是会进行相互影响的,咱们看到arr1.push('black')增加了一个新的子项,因为下面 var arr2 = arr1这行代码是将两个援用类型数据的地址指针都指向了同一块堆内存区域,所以不论是arr1还是arr2批改,任何一个改变,两个数组都是会相互产生影响的,下面的那种间接赋值的复制形式就是常说的援用类型的浅拷贝

slice:

                var arr1 = ['red','green']                var arr2 = arr1.slice(0)//复制                console.log(arr2)//['red','green']                arr1.push('black')//扭转color值                console.log(arr2)//['red','green']                console.log(arr1)//['red',;'green','black']

js数组原生办法slice会返回一个新的数组,该代码乍一看会认为是深拷贝,因为arr2和arr1互相复制牵引,而当arr1调用了push办法增加了新数组子项的时候,arr2没有发生变化,是的,这是合乎深拷贝的个性,然而拷贝的不够彻底,还不能算是真正意义上的深拷贝,所以slice只能被称为浅拷贝,slice办法只实用于一维数组的拷贝,在二维数组中就会破绽百出

                //二维数组                var arr1 = [1,2,3,['1','2','3']]                var arr2 = arr1.slice(0)                arr1[3][0]=0                console.log(arr1)//[1,2,3,['0','2','3']]                console.log(arr2)//[1,2,3,['0','2','3']]

这是一二维数组,当咱们在arr13外面进行更改arr1的值的时候,咱们发现arr1、arr2两个数组的值都产生了变动,所以事实证明slice不是深拷贝

concat:

                //一维数组                var arr1 = ['red','green']                var arr2 = arr1.concat()//复制                console.log(arr2)//['red','green']                arr1.push('black')//扭转color值                console.log(arr2)//['red','green']                console.log(arr1)//['red','green','black']                                //二维数组                var arr1 = [1,2,3,['1','2','3']]                var arr2 = arr1.concat()                arr1[3][0] = 0                console.log(arr1)//[1,2,3,['0','2','3']]                console.log(arr2)//[1,2,3,['0','2','3']]

concat办法在一维数组中是不会影响源数组的数据的,而在二维数组中concat体现和slice是一样的

js深拷贝:
js数组中实现深拷贝的办法有多种,比方JSON.parse(JSON.stringify())和递归都是能够实现数组和对象的深拷贝的

                var arr1 = ['red','green']                var arr2 = JSON.parse(JSON.stringify(arr1))//复制                console.log(arr2)//['red','green']                arr1.push('black')//扭转color的值                console.log(arr2)//['red','green']                console.log(arr1)//['red','green','black']                

事件循环

  • js的运行机制

    • 所有同步工作都在主线程上执行,造成一个执行栈(execution context stack)。
    • 主线程之外,还存在"工作队列"(task queue)。只有异步工作有了运行后果,就在"工作队列"之中搁置一个事件。
    • 一旦"执行栈"中的所有同步工作执行结束,零碎就会读取"工作队列",看看外面有哪些事件。那些对应的异步工作,于是完结期待状态,进入执行栈,开始执行。
    • 主线程一直反复下面的第三步
    • 概括即是: 调用栈中的同步工作都执行结束,栈内被清空了,就代表主线程闲暇了,这个时候就会去工作队列中依照程序读取一个工作放入到栈中执行。每次栈内被清空,都会去读取工作队列有没有工作,有就读取执行,始终循环读取-执行的操作
    • 一个事件循环中有一个或者是多个工作队列
  • 事件循环是什么

    • 主线程从"工作队列"中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。此机制具体如下:主线程会一直从工作队列中按程序取工作执行,每执行完一个工作都会查看microtask队列是否为空(执行完一个工作的具体标记是函数执行栈为空),如果不为空则会一次性执行完所有microtask。而后再进入下一个循环去工作队列中取下一个工作执行。
    • 具体阐明

      • 抉择以后要执行的宏工作队列,抉择一个最先进入工作队列的宏工作,如果没有宏工作能够抉择,则会跳转至microtask的执行步骤。
      • 将事件循环的以后运行宏工作设置为已抉择的宏工作。
      • 运行宏工作。
      • 将事件循环的以后运行工作设置为null。
      • 将运行完的宏工作从宏工作队列中移除。
      • microtasks步骤:进入microtask检查点。
      • 更新界面渲染。
  • 须要留神的是:以后执行栈执行结束时会立即先解决所有微工作队列中的事件, 而后再去宏工作队列中取出一个事件。同一次事件循环中, 微工作永远在宏工作之前执行。
  • 为什么要事件循环
    因为 JavaScript 是单线程的。单线程就意味着,所有工作须要排队,前一个工作完结,才会执行后一个工作。如果前一个工作耗时很长,后一个工作就不得不始终等着。为了协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking)等,用户代理(user agent)必须应用事件循环(event loops)。

事件冒泡和捕捉

事件流:
形容的是从页面接管事件的程序。

冒泡

IE提出的事件流叫做事件冒泡,即事件开始时由最具体的元素承受,而后逐级向上流传到较为不具体的节点

捕捉

网景公司提出的事件流叫做事件捕捉流。事件捕捉流的思维是不太具体的DOM节点应该更早接管到事件,而最具体的节点应该最初接管到事件

<!DOCTYPE html><html><head>    <title>event</title></head><body>    <div id="obj1">        welcome        <h5 id="obj2">hello</h5>        <h5 id="obj3">world</h5>    </div>    <script type="text/javascript">        var obj1=document.getElementById('obj1');        var obj2=document.getElementById('obj2');        obj1.addEventListener('click',function(){            alert('hello');        },false);        obj2.addEventListener('click',function(){            alert('world');        })    </script></body></html>
  • document>html>body>div>h5
  • 并且别离在obj1,obj2绑定了一个点击事件,因为addEvevntListener第三个参数设置为false,所以页面在冒泡阶段解决绑定事件(为true为捕捉阶段)
  • 点击文字welcome时,弹出hello。
    此时就只触发了绑定在obj1上的点击事件。

    • 具体冒泡实现过程如下:
    • welcome 属于文本节点,点击后,开始从文本节点查找,
    • 以后文本节点没有绑定点击事件,持续向上找,
    • 找到父级(id为obj1的div),有绑定的点击事件,执行,
    • 再向上找,body,没有绑定点击事件,再到html,document,都没再有绑定的点击事件,
    • 好,整个冒泡过程完结。
  • 点击文字hello时,先弹出world,再弹出hello。
  • 单机world时,弹出hello,冒泡过程和第二种状况相似。

宏工作和微工作

宏工作特色:有明确的异步工作须要执行和回调,须要其余异步线程反对
微工作特色:没有明确的异步工作须要执行,只有回调,不须要其余同步线程反对
宏工作:setTimeout,setInterval,Dom事件,Ajax
微工作:promise,async/await
执行程序:微工作=>>Dom渲染=>>宏工作

数据类型检测

typeof:

  • 间接在计算机底层基于数据类型的值(二进制)进行检测
  • typeof null 的输入是 ‘object'
  • 对象存储在计算机中,都是以000开始的二进制存储,null也是,所以检测进去是对象,但null并不是对象,能够了解为bug

instanceof:

  • 检测以后实例是否属于这个类
  • 底层机制:只有以后类呈现在实例的原型链上,后果都是true
  • 因为咱们能够肆意的改变原型的指向,多亿检测进去的后果是不准的
  • 不能检测根本数据类型

constructor:

  • 用起来看似比instanceof还好用一些(反对根本类型)
  • constructor能够轻易改,所以也不准
let arr = []console.log(arr.constructor === Array)  //truelet n = 1console.log(n.constructor === Number)  //trueNumber.prototype.constructor = ‘aa’console.log(n.constructor === Number)  //false

Object.prototype.toString.call([value])

  • 规范检测数据类型的方法:Object.prototype.toString不是转换成字符串,是返回以后实例所属类的信息

http缓存

强缓存:
当从浏览器第一次拜访一个网站,浏览器会向服务器发送申请,服务器返回资源,如果服务器感觉返回的资源是要被缓存的,js css img,就会在响应头,response header 中减少一个 cache-control,能够设置max-age:3153600 单位是s,这样浏览器就会缓存,如果设置nocache,就不会用本地缓存策略
下一次再拜访,如果没有过期,就间接从缓存拿资源,就会很快,不必再发http
第一次拜访:200 状态码
协商缓存(比照缓存):
是一种服务端缓存策略
从浏览器向服务器发申请获取资源,
如果用了该策略去拜访,服务器就会返回资源和资源标识,并且能够在浏览器把资源存到本地缓存
后续申请,不仅发送申请还会发送资源标识(last modified在响应头,if modified since 在申请头、etag)
这样服务器就会判断,以后申请的资源在缓存本地的版本和服务器是否统一,如果是最新的资源,服务器就会返回304状态码,不须要发送资源文件,并间接从缓存里拿资源
如果不是最新资源,服务器就会返回200状态码,同时把最新的资源和新的资源标识都返回,相当于残缺的申请,这就是协商缓存

资源标识:last modified 和 etag
Etag优先应用(字符串模式)
last modified在响应头,
if modified since 在申请头、

文件如果每隔一段时间都反复生成,但内容雷同,last-modified是批改工夫会每次返回资源文件,即便内容雷同
但Etag能够判断出文件内容雷同,就会返回304

webpack的loader和plugins的区别

Loader:加载器
文件加载器,运行在NodeJS中,可能加载资源文件,并对这些文件进行一些解决,比方编译,压缩等,最终一起打包到指定文件中,将A文件运行编译造成B文件,这里操作的是文件,比方将A.scss或A.less转变为B.css,单纯的文件转换过程

  • css-loader和style-loader模块是为了打包css
  • babel-loader和babel-core模块是为了把ES6代码转换为ES5
  • url-loader和file-loader是把图片进行打包的

plugin:插件
webpack运行的生命周期会播送出许多事件,plugin能够监听这些事件,在适合的机会通过webpack提供的api扭转输入后果。插件的范畴包含,从打包优化和压缩,始终到从新定义环境中的变量。插件接口性能极其弱小,能够用来解决各种各样的工作。
插件能够携带参数,所以在plugins属性传入new实例

plugins:[           //对html模板进行解决,生成对应的html,引入须要的资源模块        new HtmlWebpackPlugin({            template:'./index.html',//模板文件,即须要打包和拷贝到build目录下的html文件            filename:'index.html',//指标html文件            chunks:['useperson'],//对应加载的资源,即html文件须要引入的js模块            inject:true//资源退出到底部,把模块引入到html文件的底部        })  ]

loader和plugin的区别:

  1. loader用来加载某些资源文件,因为webpack只能了解js和json文件,对于其余如css,图片,或者其余的语法集,比方jsx,coffee,没有方法加载。这就须要对应的loader将资源转化,加载进来,字面意思也能看出,loader用于加载,它作用于一个个文件上。
  2. plugin用于扩大webpack的性能,目标在于解决loader无奈实现的其余事,间接作用于webpack,扩大了它的性能。当然loader也是变相扩大了webpack,然而它只专一于转化文件(transform)畛域。而plugin性能更加丰盛,而不仅局限于资源的加载

webpack对图片的解决

  • 因为webpack中只有js类型文件能力被辨认并且打包,其余类型的文件如css,图片等,须要特定的loader进行加载打包
  • webpack中打包图片须要用到file-loader或者url-loader加载器,这两个加载器性能根本一样,然而有区别

    • file-loader:依据配置项复制到的资源到构建之后的文件夹,并能更改对应的链接
    • url-loader:蕴含file-loader的全副性能,并且能依据配置将合乎配置的文件转换成Base64形式引入,将小体积的图片Base64引入我的项目能够缩小http申请,也是前端罕用的优化形式
  • 以url-loader为例

    • 装置url-loader(npm install url-loader --save-dev)
    • 配置选项(webpack.config.js 文件中的 module.rules 数组中增加 url-loader 的相干配置)
module:{        rules:[            {                test: /\.(png|jpg|gif|svg)$/,                use: 'url-loader'            }        ]}
  • 要想打包后图片名称不变,并且可能增加到指定目录下,能够在 rules 中增加一个 options 属性
        rules:[            {                test: /\.(png|jpg|gif|svg)$/,                use:[{                    loader: 'url-loader',                    options: {                        name: 'images/[name].[ext]'                    },                }]            }        ]
  • 优化图片(压缩图片大小)装置image-webpack-loader
  • 批改webpack.config.js配置
{    test: /\.(png|jpg|gif|svg)$/,    use: [        {            loader: 'url-loader',            options: {                limit: 10000,                name: 'images/[name].[ext]'            }        },        {            loader:'image-webpack-loader',            options: {                bypassOnDebug: true,            }         }    ]}

nginx的反向代理和webpack的代理区别

  • node.js反向代理,替换的只是原申请地址的域名,间接把标识符之前的内容间接替换,不是标识符的所有内容
  • nginx反向代理,nginx是要依据后盾的理论状况来解决,有可能是间接把标识符及之前的内容都替换掉,也有可能是只替换标识符之前的内容。

跨域

跨域问题其实就是浏览器同源策略导致的

  1. 同源策略

    • 用于限度一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮忙阻隔歹意文档,缩小可能被攻打的媒介----MDN
  2. 什么才是同源

    • 只有当协定(protocol),域名(domain),port(端口)三者统一才是同源
  3. 如何解决跨域

    • CORS
    • 跨域资源共享(CORS)是一种机制,它应用额定的HTTP头来通知浏览器 让运行在一个origin(domain)上的web利用被准许拜访来自不同源服务器上的指定的资源。当一个资源与该资源自身所在的服务器不同的域,协定或端口申请一个资源时,资源会发动一个跨域HTTP申请
    • 浏览器反对状况IE>9,opera>12,FF>3.5。比这些版本小的用JSONP
    • 简略申请:

      • 不会触发CORS预检申请。(该术语不属于Fetch标准)。若申请满足下述所有条件,则该申请可视为 ‘简略申请’
      • 状况一:应用以下办法(除了以下申请外的都是非简略申请)
      • GET
      • HEAD
      • POST
      • 状况二:人为设置以下汇合外的申请头
      • Accept
      • Accept-Language
      • Content-Language
      • Content-Type (须要留神额定的限度)
      • DPR
      • Downlink
      • Save-Data
      • Viewport-Width
      • Width
      • 状况三:Content-Type的值仅限于下列三者之一:(例如 application/json 为非简略申请)
      • text/plain
      • multipart/form-data
      • application/x-www-form-urlencoded
    • cors中的cookie问题:

      • 想要传递cookie须要满足三个条件
      • web申请设置withCredentials(这里默认状况存在跨域申请,浏览器是不带cookie的)
      • Access-Control-Allow-Credentials 为 true
      • Access-Control-Allow-Origin为非 *
      • 这里申请的形式,在 chrome 中是能看到返回值的,然而只有不满足以上其一,浏览器会报错,获取不到返回值。
    • cors申请头:

      • Access-Control-Allow-Origin:批示申请的资源能共享给哪些域

    能够是具体的域名或者*示意所有域。

     * Access-Control-Allow-Credentials:批示当申请的凭证标记为 true 时

    是否响应该申请。

     * Access-Control-Allow-Headers:用在对预申请的响应中,

    批示理论的申请中能够应用哪些 HTTP 头。

     * Access-Control-Allow-Methods:指定对预申请的响应中,

    哪些 HTTP 办法容许拜访申请的资源。

     * Access-Control-Expose-Headers:批示哪些 HTTP 头的名称能在响应中列出。 * Access-Control-Max-Age:批示预申请的后果能被缓存多久。 * Access-Control-Request-Headers:用于发动一个预申请,

    告知服务器正式申请会应用那些 HTTP 头。

     * Access-Control-Request-Method:用于发动一个预申请,

    告知服务器正式申请会应用哪一种 HTTP 申请办法。

     * Origin:批示获取资源的申请是从什么域发动的
    • Node正向代理
    • 代理思路为,利用服务端申请不会跨域的个性,让接口和以后站点同域
    • cli 工具中的代理

      • 在cli 工具中中配置proxy来疾速获取接口代理的能力
    • Nginx反向代理
    • 通过反向代理的形式,(这里须要自定义一个域名)这里就是保障我以后域,能获取到动态资源和接口,不关怀是怎么获取的。
    • JSPON
    • 次要利用了script标签没有跨域限度这个个性来实现的(仅反对GET办法)

      • 流程
      • 前端定义解析函数(例如jspCallback=function(){...})
      • 通过params模式包装申请参数,并且申明执行函数(cb=jspCallback)
      • 后端获取前端申明的执行函数(jspCallback),并以带上参数并调用函数的形式传递给后端
    • window.name+Iframe

      • window对象的name属性是一个很特地的属性,当该window的location变动,而后从新加载,它的name属性能够仍然放弃不变
      • 通过iframe的src属性由外域转向本地区,跨域数据既由iframe的window.name从外域传递到本地区。这个就奇妙绕过了浏览器的跨域拜访限度,但同时又是平安操作
    • 浏览器开启跨域(非必要状况)
    • 注意事项: 因为浏览器是泛滥 web 页面入口。咱们是否也能够像客户端那种,就是用一个独自的专门宿主浏览器,来关上调试咱们的开发页面。例如这里以 chrome canary 为例,这个是我专门调试页面的浏览器,不会用它来拜访其余 web url。因而它也绝对于平安一些。当然这个形式,只限于当你真的被跨域折磨地解体的时候才倡议应用以下。应用后,请以失常的形式将他关上,免得你不小心用这个模式干了其余的事。
    • windows环境

      • 装置目录
      • .\Google\Chrome\Application\chrome.exe --disable-web-security --user-data-dir=xxxx