关于javascript:2021应届秋招前端面经一JavaScript部分

11次阅读

共计 28746 个字符,预计需要花费 72 分钟才能阅读完成。

JavaScript

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

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

  1. 赋值:原始类型赋(值),援用类型赋(援用)
// 原始类型赋值(真的赋值,内存空间 1)let str1 = 'hello'
let str2 = str1
str1 = 'world'
console.log(str1) //world
console.log(str2) //hello
        
// 援用类型的赋值(赋援用,在内存空间 1 申明,在内存空间 2 赋值)let stu1 = {name: 'xm'}
let stu2 = stu1
stu1.name = {name: 'xh'}
console.log(stu1.name)  //xh
console.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)//false
let stu2 = stu1
console.log(stu1===stu2)//true
  1. 函数传参:原始类型作为参数,函数内的操作不影响实参的值。援用类型作为参数,函数内的操作会影响实参的值。
// 原始类型传参
let fn1 = function(num) {num = 100}
let n = 10
fn1(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);//true
alert(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)  //true

let n = 1
console.log(n.constructor === Number)  //true

Number.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
正文完
 0