前端杂货铺

224 阅读17分钟

复习过程中的记录

JS 基础

- 作用域

一个变量合法的使用范围

1. 全局作用域

代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域

2. 函数作用域

在固定的代码片段才能被访问

const demoFunction = () => { const str = '函数作用域' console.log(str) }

3. 块级作用域

在固定的代码片段才能被访问

if(true){ let a = '块级作用域' console.log(a) }

在当前作用域中没有定义但是被使用了的变量,会向上级作用域一层层寻找,直到找到为止,如果到了全局作用域都没有被找到 将会被报出 XXX is not defined 这个查找的过程形成的链条就叫做作用域链

- 闭包

函数和函数内部能访问到的变量的总和

const closure = () => {
    let num = 1
    return function(){
        console.log(num)
    }
}

具体表现

1.函数作为返回值

var num = 2
const exec = closure()
exec() // 输出结果 --> 1

1.函数作为参数
const demoExec = (fn) => {
    let num = 2
    fn()
}

demoExec(closure) // 输出结果 --> 1 

闭包:一个函数的作用域能够访问另一个函数作用域里面定义的变量的值,变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方

闭包的作用是使代码间接的访问一个变量,定义变量如果在全局的时候定义,有可能会被人不小心修改到当前的值,而闭包中的定义的变量只能在当前作用域访问,可以提供给全局唯一的操作方法来操作当前的变量。

ES6 的新的特性

1.const 和let

声明局部便变量,可以在{}代码块中定义,作用域就在当前的代码块中可以查看之前的文档

2.字符串模板的使用

3.箭头函数

不用定义function 关键字,不需要 return 关键字返回,继承当前上下文this

4.函数参数可以有默认值

5.对象和数组的解构

for in 和 for of

for..in 遍历的是索引key,而for of遍历的是数组元素值value。

1.for in

更适合遍历对象,当然也可以遍历数组,但是会出现一点问题,首先索引是字符串,不能进行一些计算,其次遍历顺序可能不能按照数组顺序,会遍历数组所有的可枚举属性,包括原型,如果不想遍历原型方法和属性的话,可以在循环内部判断一下,使用hasOwnProperty()方法可以判断某属性是不是该对象的实例属性。

1.for of

遍历的是值,不遍历对象 Uncaught TypeError: obj is not iterable

- this

this取什么值是在函数调用的时候确定

1. 作为普通函数
2. 使用call,apply,bind
3. 作为对象方法被调用
4. class方法

对象的方法不能使用箭头函数,箭头函数的对象方法的this 会是空

5. 箭头函数

箭头函数this取值永远都是上级作用域的this

bind、call、apply

都是重新定义函数的this对象的,函数的第一个参数都是this 的指向对象,bind 和 call 的原函数参数按顺序放入,apply 的剩余参数按照数组的形式放入。

1.bind

绑定新的this,并且返回一个新的函数

Function.prototype._myBind = function(){
    const arg = Array.from(arguments)
    const ctx = arg.shift()
    const _this = this      
    return function(){
        return _this.apply(ctx,arg)
    }
}
2.call、apply

绑定新的this,并且返回函数执行结果

Function.prototype._myCall = function(){
    const arg = Array.from(arguments)
    const ctx = arg.shift()
    ctx.fnc = this
    // call 的传参
    const res =  ctx.fnc(...arg)
    // apply 的传参
    const res =  ctx.fnc(...arg[0])
    delete ctx.fnc
    return res
}

上述代码只是简易流程,去除了部分的参数的校验

- typeof 和 # instanceof

1.typeof

操作符返回一个字符串,表示的是未经计算的操作数基础类型,能够识别基本的数据类型,能够识别函数,能够识别引用类型,但是不能够再细分了。

js的基础类型:

  1. string
  2. number
  3. boolean
  4. object 返回值为object的为引用类型,还有包括为null的 引用类型的存储方式,正常的值类型是以键值得方式存储在栈中的,引用类型的存储方式是数据存储在堆中,堆得地址作为值,与变量绑定存储在栈中,所以当复制的时候就会出现深复制和浅复制的问题
// 手写深复制
const deepClone = function(obj){
    if(typeof obj !== 'object' || obj === null) return obj
    let result = obj instanceof Array ? [] : {}
    for(let key in obj){
       // 判断是自身的属性,不是从父类那边继承而来
        if(obj.hasOwnProperty(key)){
            result[key] = deepClone(obj[key])
        }
    }
    return result
}
  1. function
  2. undefined typeof a => undefined 表示为未定义
  3. symbol es6新增

js在底层存储变量的时候,会在变量的机器码的低位存储存储类型信息

  • 000 表示对象
  • 010:浮点数
  • 100:字符串
  • 110:布尔
  • 1:整数
  • 机器码均为0:null
  • −2^30: undefined

所以造成了typeof null => object 的历史遗留问题

2.instanceof

运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上(前面的原型链上有没有后面的原型对象)

-原型与原型链

1.显式原型与隐式原型

// 定义一个类型
class People {
    constructor(name,age){
       this.name = name
       this.age = age
    }
    introduce(){
        console.log(`你们好!我的名字是${this.name},我的年龄是${this.age}`)
    }
}
const tom = new People('Tom',26)

显式原型(prototype)

console.log(tom.prototype) // undefined
console.log(People.prototype) //People {}

构造函数才具备 显示原型 显示原型即自身所附带的属性、方法的集合

隐式原型

console.log(tom.__proto__) //People {}
console.log(People.__proto__) // Function

每个实例具备一个隐式原型,每个实例的隐式原型 都指向对应构造函数的显式原型

实例当中的方法执行规则

console.log(tom.introduce())  // 你们好!我的名字是Tom,我的年龄是26

1.先在自身属性和方法上面去查找

2.如果没有找到就到 _proto_ 上面去查找

- Promise

- 函数声明和函数表达式

console.log(demo1()) // 函数声明
console.log(demo2()) // undefined
function demo1(){
    return '函数声明'
}
const demo2 = function(){
    return '函数表达式'
}

函数声明能够提前预加载,就像是变量提升一样的效果

- Map 和 Object 区别 ***

1.Map可以以任意类型为key

2.Map是有序结构,Object是无序结构

3.Map操作是很快的

- Set 和 数组 区别 ***

1.api不同

2.Set元素不能重复

3.Set是无序结构,操作很快

-防抖和节流

防抖(debounce) 超过一定时间间隔事件才会触发

function debounce(fn,delay){
    let timer = null
    return function(){
        if(timer !== null){
            clearTimeout()
        }
        timer = setTimeout(()=>{
             fn()
        },delay)
    }
}

节流(throttle) 规定时间内事件只会触发一次

function throttle (fn,delay){
  let timer = null
  return function(){
      if(!timer){
         timer = setTimeout(()=>{
            fn()
            timer = null
         },delay)
      }
  }
}

浏览器相关

浏览器输入网址发生了什么

1.输入域名之后,浏览器向最近的DNS服务器发起请求解析域名Ip

浏览器首先会匹配缓存如果缓存当中有之前解析过的域名与IP的映射的话,就直接发起后续请求,如果没有命中对应的缓存的话,就向公网当中的DNS服务器发起请求。

2.建立TCP连接

通过浏览器与服务器的TCP三次握手请求之后建立连接,接着可以进行后续的http 和 https的请求。

3.浏览器发送请求给服务端

4.服务端响应请求返回数据给浏览器

5.浏览器根据返回内容将其渲染在浏览器上

浏览器接收到了服务返回的脚本、文本、资源等然后通过浏览器的渲染机制将页面呈现在用户面前。

script 标签引入方式

image.png 正常情况下,js文件的加载解析将暂停Dom tree的构建,因为并不清楚js 文件中是否有操作dom的代码片段,于是当返回中有js文件时,浏览器会暂停HTML parser的解析,如果js文件比较大,这样就会导致加载时间过长,我们可以将js的文件加载跟解析同步实现,但是文件的执行根据不同的关键字deferasync来分别控制,defer放在解析完成之后运行,async放在了下载完成之后立即运行。

浏览器的渲染机制(用户看到的页面)

只是简述了大概的步骤没有做到所有情况例如js阻塞、html解析过程对于渲染的影响等,好文推荐 segmentfault.com/a/119000000…

1.html文件解析成 DOM Tree
将返回的html文件解析成为DOM Tree ,这个的过程中浏览器还是会并行的请求css、图片等资源。

2.CSS 文件解析成 styleRule
将返回的CSS文件解析成styleRule(样式表对象)浏览器如何解析css选择器

3.将DOM Tree 和 styleRule 结合生成Layout Tree
将上述解析完成的styleRule样式表对象合并到Dom Tree上形成渲染引擎最后要执行的layout Tree

4.将layout Tree布局(Layout)阶段
将上述生成的layout Treee中的元素确定大小、位置。

4.浏览器GPU进行绘制(Painting)
将布局树的各个节点绘制到屏幕上,本质上是一个本质上是一个像素填充的过程。这个过程也出现于回流或一些不影响布局的 CSS 修改引起的屏幕局部重画,这时候它被称为重绘(Repaint)。实际上,绘制过程是在多个层上完成的,这些层我们称为渲染层(RenderLayer)

5.渲染层的合并
多个绘制后的渲染层按照恰当的重叠顺序进行合并,而后生成位图,最终通过显卡展示到屏幕上。

参考:juejin.im/post/684490…

事件循环机制

javascript 在函数的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列来异步的处理一些另外的代码,主线程从“任务队列”中读取事件并且执行的整个过程是不断循环的,所以称为事件循环。函数调用栈每次从任务队列中获取一个任务前都会检查微任务队列是否清空,如果不为空则会一次性执行完所有的为微任务。

宏任务:全局script 代码片段setTimeoutsetIntervalsetImmediateI/OUI rendering 微任务:promiseMutationObserver

事件绑定,事件冒泡、事件捕获、事件代理

1.事件绑定:

  • 为DOM元素增加动作事件,可以通过标签内的html 属性绑定
  • 通过js对象绑定事件
  • 通过绑定DOM的监听函数实现

2.事件冒泡 当一个元素触发事件的时候,会将事件传递给自己的父级,一直到window仅仅传递绑定事件的类型,不会传递执行函数,如果父级没有绑定对应事件,则不执行 可以使用stopPropagation 去组织冒泡

3.事件捕获

事件冒泡相反 是从父元素往里传递事件,但是现在浏览器一般是事件冒泡

4.事件代理

添加到页面上的事件数量的多少也会影响到整体的性能,当父元素底下有许多子元素的点击事件触发函数相同的时候,可以交给父元素来触发。

状态码

请求状态码表示了当前请求发出之后的响应结果能够帮助开发者快速定位问题

1XX

100:表示请求成功,可以发起后续的请求。

2XX

200:请求成功,返回响应值

204:请求成功,但是响应头后面没有body数据

3XX

301:永久重定向,当你以前的站点不用了之后就应该返回301,浏览器缓存会优化,在第二次访问的时候直接访问重定向网址

302:临时重定向,暂时需要访问别的网站,不优化浏览器缓存

304:协商缓存命中,浏览器读取缓存

4XX

400:Bad Request 表示请求错误,但是不知为何,不够像别的状态码范围那么小

401:用户没有访问权限,要求身份验证

403:服务器禁止访问

404:资源未被找到,在服务器上为找到响应的资源

405:请求方法错误。不被服务器允许

408:请求超时

5XX

500:服务错误 502:服务没错,但是访问出错

ajax 和 fetch

1 ajax 是XMLhttpRequest对象的一个实例 fetch 是window底下的一个方法

2 fetch()返回的promise 不会拒绝http的错误状态如404 或者 500,只有当网络请求与错误的时候才会拒绝

3 默认情况下两者都不会接受或者发送cookies

XSS 和 CSRF 攻击

1. Xss 跨站脚本攻击 恶意攻击者往web网页里面插入恶意可以执行的脚本代码,当用户在浏览该页之时,嵌入其中的脚本代码会被执行,从而可以达到攻击者盗取用户信息或其他侵犯用户安全隐私的目的。

2. CSRF 跨站点伪造 诱导用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起跨站点请求


CSS相关

CSS 选择器

1.选择器类型

id选择器、元素选择器、类选择器、伪元素选择器、伪类选择器、子代选择器....

1.优先级 !important > 内联 > ID > 标签 || 伪类 || > 伪对象

浏览器是怎么解析CSS选择器的

浏览器对于Css选择器的解析是通过从右向左的方式进行解析,若通过从左到右的方式当父节点符合要求时,子节点有多个就不可避免的存在需要回溯这样是浪费性能的,如果是通过从右到左的方式的话,可以在第一步就可以筛选掉不符合的子节点,切每个子节点有唯一的父节点,即可通过匹配父节点确定。

二、浏览器的渲染机制(用户看到的页面)


只是简述了大概的步骤没有做到所有情况例如js阻塞、html解析过程对于渲染的影响等,好文推荐 segmentfault.com/a/119000000…

1.html文件解析成 DOM Tree
将返回的html文件解析成为DOM Tree ,这个的过程中浏览器还是会并行的请求css、图片等资源。

2.CSS 文件解析成 styleRule
将返回的CSS文件解析成styleRule(样式表对象)浏览器如何解析css选择器

3.将DOM Tree 和 styleRule 结合生成Layout Tree
将上述解析完成的styleRule样式表对象合并到Dom Tree上形成渲染引擎最后要执行的layout Tree

4.将layout Tree布局(Layout)阶段
将上述生成的layout Treee中的元素确定大小、位置。

4.浏览器GPU进行绘制(Painting)
将布局树的各个节点绘制到屏幕上,本质上是一个本质上是一个像素填充的过程。这个过程也出现于回流或一些不影响布局的 CSS 修改引起的屏幕局部重画,这时候它被称为重绘(Repaint)。实际上,绘制过程是在多个层上完成的,这些层我们称为渲染层(RenderLayer)

5.渲染层的合并
多个绘制后的渲染层按照恰当的重叠顺序进行合并,而后生成位图,最终通过显卡展示到屏幕上。

参考:juejin.im/post/684490…


Vue 相关

响应式原理

class Vue {
    constructor(options){
        this.data = options.data
        // this.el = options.el
        this.el = document.querySelector(options.el)
        // 响应式数据
        new Observe(this.data)
         
        // 编译模板
        new Compile(this.el,this)
    }
}
function reactiveProperty(data,key,value){
    const dep = new Dep()
    Object.defineProperty(data,key,{
        enumerable:true,
            configurable:true,
            get:function(){
                //  每个值对应的依赖收集的选项
                 if(Dep.target !== null){
                     dep.depend(Dep.target)
                 }
                 return value
            },
            set:function(nValue){
                value = nValue
                dep.notify()
            }
    })
}
class Observe {
    constructor(data){
       Object.keys(data).forEach(key => {
         reactiveProperty(data,key,data[key])
       })
    }
}
class Dep {
    constructor(){
        this.dep = []
    }
    depend(target){
        if(this.target !== null){
            this.dep.push(target)
        }
    }
    notify(){
        console.log(this.dep)
        if(this.dep.length > 0){
            this.dep.forEach(d => {
                console.log(d)
                d.update()
            })
        }
    }
}
class Watch {
        // 当前的一个node元素
    constructor(el,data,key){
        // 当前的一个node元素
        this.el  = el
        Dep.target = this
        this.key = key
        this.data = data
        el.textContent = data[key]
        Dep.target = null
    } 
    update(){
        this.el.textContent = this.data[this.key]
    }
}

class Compile {
    constructor(el,vm){
        this.el = el
        el.childNodes.forEach(n => {
            new Watch(n,vm.data,n.textContent)
        })
    }
}

const vue = new Vue({
    data:{
        a:1,
        b:2,
        c:3,
    },
    el:'#app'
})

VUE 首屏优化

单页面应用在访问根目录之后会将所有页面的资源都返回并且并且加载,如果首屏需要加载过多,过大的文件内容的时候就会出现一定程度的白屏

1.路由懒加载

使用箭头函数 懒加载import 组件

component: () = >import( /* webpackChunkName: "Login" */ '@/view/Login')

2.组件懒加载

与路由懒加载同理

3.CDN资源优化

CDN 调用全家桶资源,使用打包工具将引入资源配置。

4.gzip加速优化

webpack插件配置将打包文件进行gzip压缩,这样浏览器加载资源的时间变小,现代浏览器都支持gzip文件的解压展示,当gzip没有访问到的,可以访问同名源文件,所以源文件也要一并上传

5.SSR 服务端渲染

6.首页加loading 骨架屏

7.雪碧图

为什么v-for的时候要加入key值

v-for 的默认渲染行为是会尝试原地修改元素而不是移动他们,要强制其重新排序元素,你需要特殊的标识符key

例如在一个数组中的某个位置增加一个元素

image.png

当我们使用v-for没有增加key值作为唯一标识符的时候,diff算法默认使用的是“就地复用”查询当前位置下的两个元素是否相同,如果不同直接删除原有元素,添加新元素。如果这样的话从F之后的元素都要重新计算

image.png

但是如果是用key作为唯一的标记来标记的话,我们就可以发现diff算法会先去寻找是否有对应的id的元素。这样就可以减少原有元素的操作了

image.png

nextTick 的原理

Vue在响应式更新Dom的时候是异步执行promisemutationObserver 然后将函数执行放在setImmediate 里如果执行环境不支持的话会使用setTime之中了,根,当监听到数据的变化的时候,会开启一个队列,缓冲一个事件中的所有的数据变更,如果一个数据变更多次则会去重留下最后一次的更新操作,结合事件循环机制,调用nextTick 的回调函数前会将 微任务中的任务全部执行完毕,这样就可以实现nextTick

Object.defineProperty 和 Proxy

1.Object.defineProperty

通过设置可选项配置一个对象,返回一个新的对象。

1.劫持对象的如果是数组的话,无法监听到数组长度的变化,因此vue中有重写了一些数组的方法

2.不能监听对象的添加

3.只能劫持对象的属性,需要在数据劫持的阶段对每个属性进行遍历,所以如果考虑到性能问题因此 数组的遍历的劫持,并不包括其中

2.Proxy 数据的操作的拦截器,不会修改到原有对象,只会生成一个拦截器的对象

1.可以监听数组length属性的变化

2.可以监听对象的添加,可代理整个对象,不需要对对象进行遍历,极大提高性能。

3.多达13种的拦截远超Object.defineProperty只有get和set两种拦截。

修饰符

.lazy v-model修饰符,加了lazy之后不再是实时响应,而是失去焦点之后v-model的值才会修改

.trim 字符串的trim的效果一致,收尾空格过滤掉

.number 将数值转换成数字,先输入数字则去有效数字,如果一开始就是字符串的话就是无效

.capture 事件捕获,从外层开始往里面传递,先执行外部的事件,再执行内部的事件

.stop 阻止冒泡,只执行事件,不向上冒泡

.self 只有点击自身才会触发事件

.once 只会触发一次

.prevent 阻止默认事件,例如a标签的跳转

.native 加在自定义组件上面确保事件能够执行

.sync 根据子组件的触发的update的值确定前缀