前端学习笔记


 JavaScript

 浏览器分成了两个部分:渲染引擎和js引擎

  • 渲染引擎:用来解析HTML与CSS,俗称内核,比如chrome浏览器的blink,老版本的webkit
  • JS引擎:俗称JS解释器,用来读取网页中JavaScript代码,对其处理后运行,比如chrome浏览器的V8 

JS的组成

  • ECMAScript:javascript语法
  • DOM:页面文档对象模型
  • BOM:浏览器对象模型

数据类型的分类:

  • 总:USONB(U: undefined; S: string, symbol; O: object; N: null, number; B:boolean) 

  • 简单数据类型: undefined、null、boolean、number、string
  • 复杂数据类型:Object、Array、Date
  1. 简单数据类型在存储变量中存储的是值本身,而复杂数据类型(引用类型)在存储变量时存储的仅仅是地址
  2. 简单数据类型变量的值直接存放在栈空间中,而引用类型变量栈空间存放的是地址,真正的对象实例存放在堆空间中

 数据类型的隐式转换

// 转换成string:+(字符串连接符) +=(后面跟着一个字符串)

let num = 1, bool = true

console.log( 1 + '1' )    // '11'
console.log( 1 + 'true' ) // '1true'
console.log( num + '' )   // '1'
console.log( bool += '1') // 'true1'


// 转换成number:++/--(自增自减运算符) +-*/%(算数运算符)> < >= <= == != === !== (关系运算符)

let str = '1', bool = true

console.log( str ++ )    // 输出1,但是str=2
console.log( '6' + 7 )   // 输出'67',因为这里的加号是字符串连接符
console.log( '7' - 6 )   // 输出1
console.log( true + 1 )  // 输出2,true被转换为1


// 转换成Boolean类型:!(逻辑非运算)

console.log( !!1122 )    // true
console.log( !'laka' )   // false
console.log( !'' )       // true
console.log( !0 )        // true

数据类型隐式转换常见面试题

      1.字符串连接符与算数运算符

console.log( 1 + 'true' )    // '1true'
console.log( 1 + true )      // 2
console.log( 1 + undefined ) // NaN
console.log( 1 + null )      // 1
console.log( 1 + [] )        // '1'
console.log( 1 + [1, 22] )   // '11,22'

// 原因分析
// 1. 当+左右两边存在字符串时,+会变成字符串连接符
// 2. Number(true) = 1
// 3. Number(undefined) = NaN, NaN与任何数做任何运算都是本身
// 4. Number(null) = 0, 但是 null == undefined 结果为true
// 5. 此时在做运算的时候, [] 先转换为字符串'', 因此结果为'1'
// 6. 同理,[1, 22]先转换为'1,22'

       2.关系运算符

console.log( '2' > 10 )          // false
console.log( '2' > '10' )        // true
console.log( 'abc' > 'b' )       // false
console.log( 'abc' > 'aab' )     // true
console.log( NaN == NaN )        // false
console.log( undefined == null ) // true

// 原因分析
// 当关系运算符左右两边为数字和字符串时,字符串先转换为数字
// 当两边都为字符串时,按照字符串对应的Unicode编码比较
// 1. 2 > 10
// 2. '2'.charCodeAt() > '10'.charCodeAt(), 值得注意的是,使用charCodeAt方法只会返回字符串第一个字符的unicode编码,因此'10'.charCodeAt == '1'.charCodeAt()
// 3. 与2同理
// 4. 当两个字符串同一位置的字符相同时,会比较下一个字符
// 5和6均为特殊情况,Number(undefined) != Number(null)

      3.复杂数据类型

console.log( [11, 22] == '11,22' )      // true
console.log( [67] == 67 )               // true 

// 分析:
// 当复杂类型与字符串比较时,首先调用valueOf()方法返回对象的原始值
// 然后再调用toString()方法得到字符串进行比较
// 如果是与number进行比较的话,还会使用Number()函数将字符串转换成数字进行比较


let o = {}
console.log( o == '[object, Object]' )  // true
console.log( o.valueOf() )              // {}
console.log( o.valueOf().toString() )   // '[object, Object]'

// 分析:
// 任何对象调用toString()方法后得到的字符串都是'[object Object]'
// 前后两个object的o大小写不一样


let o = {
    value: 0,
    valueOf: function(){
        return ++ this.value
    }
}
console.log( o == 1 )                   // true
console.log( o == 2 )                   // true
console.log( o == 3 )                   // true
console.log( o == 3 )                   // false

// 分析:
// 值得注意的是,对象的valueOf()方法是可以重写的
// 每次进行关系比较时,都会隐式调用valueOf()方法,因此value不断加一

       4.一些坑

console.log( [] == 0 )        // true
console.log( ![] == 0 )       // true

// 分析:
// 1.当对象与数字进行比较时,对象会进行转换Number([].valueOf().toString())
// 空字符串会转换成0,因此[]==0
// 2.此时的空数组前面有个取反符!,逻辑运算符的优先级比算数运算符高,因此左边会先转换成Boolean类型
// 此时值得注意的是 Boolean([]) === true
// 这是因为空数组的类型为Object,而Boolean()函数中的参数,只要数据类型为对象那么都会得到true


console.log( [] == ![] )      // true
console.log( [] == [] )       // false

// 分析:
// 1.可以发现==右边又有一个!,那么右边的数据类型为Boolean,为false
// 左边的数据类型为对象,此时,对象并不是直接转换为Boolean类型与右边比较的
// 而是将对象先转换为字符串,在转换为数字进行比较,Number([].valueOf().toString())==0
// 同理Boolean类型也要转换为数字进行比较,![] == 0
// 2.数组是对象,为引用类型数据,数据存储在堆中,栈中存储的是地址
// 虽然都是空数组,但是起始地址不相同,所以两个空数组也不同,因此[1, 2] == [1, 2]也为false


console.log( {} == {} )       // false
console.log( {} == !{} )      // false

// 分析:
// 1.经过上一题的洗礼,不难得出结果,两个空对象的地址不同,因此为false
// 2.同理,左右两边转换为number进行比较
// 先看右边,对象转换为Boolean一定为true,取反后再用Number()函数可以得到0
// 再看左边,调用toString()方法后得到'[object, Object]',再使用Number()函数得到NaN
// NaN == 0 为false

      5.做个总结?

  1. 任何数据类型与对象相加都会变成string类型
  2. '2' > '10'
  3. undefined == null
  4. Boolean([]) === Boolean({}) === true
  5. Obje转换为number都会得到NaN
  6. Object(valueOf()和toString())=>String(Number())=> Number <= Boolean

数据类型的判断 

typeof 

typeof 变量,返回变量的数据类型,返回的结果是一个字符串 

【返回结果】 

返回值包括:'undefined', 'object', 'boolean', 'number', 'bingint', 'string', 'symbol', function' 

instanceof 

object instanceof constructor 用于判断constructor是否出现在object实例对象的原型链上,一般是用来判断具体的对象类型

【返回结果】 

返回值为波尔类型,只有true或者false  

var simpleStr = 'this is a simple string'
var myStr = new String()

simpleStr instanceof String    // false
myStr instanceof String        // true

 【手写instanceof】

function instance_of (L, R) {
  let l = L.__proto__
  let r = R.prototype
  while (true) {
    if (l === null) {
      return false
    }
    if (r === l) {
      return true
    }
    l = l.__proto__
  }
}

IIFE(Immediately-Invoked Function Expression) 

【作用】 

  • 隐藏实现
  • 不会污染外部命名空间
  • 用来编码js模块 
;(function () {
  var a = 1
  function test () {
    console.log(++a)
  }
  window.$ = function () {
    return {
      test: test
    }
  }
})
$().test() // 2


预解析

 JS引擎运行JS的时候分为两步预解析代码执行

预解析:JS引擎会把JS里面所有的var还有function提升到当前作用域的最前面

代码执行:按照代码书写的顺序从上往下执行

预解析分为 变量预解析(变量提升) 和 函数预解析(函数提升) 

变量提升:把所有的变量声明提升到当前的作用域最前面,不提升赋值操作

函数提升:把所用的函数声明提升到当前的作用域最前面,不调用函数

先执行变量提升后执行函数提升

 一个小案例

f()
console.log(c)
console.log(b)
console.log(a)
function f(){
    var a = b = c = 9
    console.log(a)
    console.log(b)
    console.log(c)
}

// 输出:9 9 9 9 9 error(报错)
// 问题出在这一句:var a = b = c = 9
// 相当于 var a = 9; b = 9; c = 9;
// 所以a只是局部变量,而b和c是全局变量
var a
function a() {}
typeof a // 'function'

var fun = 1
function fun() {}
fun() // 报错
// 当函数与变量同名的时候,函数会覆盖变量
// 但是后面又执行了赋值操作,所以fun = 1


对象

构造函数:

// 构造函数的语法格式
function 构造函数名() {
    this.属性 = 值;
    this.方法 = function() {}
}

// var object = new 构造函数名()
// new关键字执行过程
// 1. new 构造函数在内存中创建了一个空的对象
// 2. this 就会指向刚创建的空对象
// 3. 执行构造函数里面的代码,给这个空对象添加属性和方法
// 4. 返回这个对象(所以构造函数里面不需要return)

function _new (constructor, ...arg) {
  // 创建一个空对象
  let obj = {}
  // 空对象的隐式原型指向构造函数的显式原型,为这个新对象添加属性
  obj.__proto__ = constructor.prototype
  // 构造函数的作用域赋给新对象
  let res = constructor.apply(obj, arg)
  // 返回新对象,如果没有显式return语句,则返回this
  return Object.prototype.toString.call(res) === '[object Object]' ? res : obj
}

原型对象:

 解析器会向每一个函数中添加一个prototype属性,无论是构造函数还是普通的函数,这个属性对应着一个对象,这个对象就是原型对象

 如果函数作为普通的函数调用,则prototype没有任何作用,但是如果作为构造函数创建对象时,它所创建的对象都会有一个隐含属性__proto__该属性指向构造函数的原型对象

 原型对象就相当于一个公共区域,所有同一个类的实例对象都可以访问到这个原型对象,值得注意的是,原型对象中也有__proto__属性,只有Object对象的原型没有原型

检查对象是否含有某种属性或方法以及原型链

function Person(name, age){
    this.name = name
    this.age = age
}
Person.prototype.sayHello(){ console.log('hello') }

let xiaoli = new Person('xiaoli', 18)
console.log( 'sayHello' in xiaoli )                //true
console.log( xiaoli.hasOwnProperty('sayHello') )   // false

console.log( xiaoli.__proto__)                     // [object Object] xiaoli的原型
console.log( xiaoli.__proto__.__proto__ )          // [objrct Obiect] xiaoli原型的原型,相当于Object的原型
console.log( xiaoli.__proto__.__proto__.__proto__ )// null Object的原型没有原型

总结: 

  1. 所有的引用类型(数组、对象、函数),都具有对象的特性,即可以自由扩展属性('null')除外
  2. 所有的引用类型,都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
  3. 所有的函数,都有一个prototype(显式原型)属性,属性值也是一个普通的对象
  4. 所有的引用类型,__proto__属性值指向(完全相等)它的构造函数的prototype属性值
  5. 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么就会去__proto__(即它的构造函数的prototype中)寻找 
  6. 函数的显式原型指向的对象默认是空Object对象(除Object外)
  7. 所有函数都是Function的实例(包括Function)
  8. Object的对象是原型链的尽头(Object.prototype.__proto__ === null)


this

this出现的原因: 

JavaScript允许在函数体内部,引用当前环境的其他变量,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context),所以this出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境

解析器再调用函数的时候会向函数内部传递一个隐含参数this

this为函数执行的上下文对象,谁调用它,this就指向谁

严格模式下,this默认绑定在undefined上,非严格模式下才是window 

var name = 'window'

var fun = function () {
  console.log(this.name);
}

var obj1 = {
  name: 'obj1',
  sayName: fun
}

var obj2 = {
  name: 'obj2',
  sayName: fun
}

fun()            // 'window'
obj1.sayName()   // 'obj1'
obj2.sayName()   // 'obj2'
function foo () {
  console.log(this.a)
}

var a = 2

var obj1 = {
  a: 3
}

var obj2 = {
  a: 4
}

var bar = function () {
  foo.call(obj1)
}

bar() // 3
setTimeout(bar, 100) // 3

bar.call(obj2) // 3 !!!
// 虽然bar被显式绑定到了obj2上,但是foo已经被绑定在了obj1上,所以在foo函数内部,this指向obj1
var a=1;
function printA(){
  console.log(this.a);
}
var obj={
  a:2,
  foo:printA,
  bar:function(){
    printA();
  }
}

obj.foo(); //2
obj.bar(); //1
var foo=obj.foo;
foo(); //1
var length = 10;

function fn() {
    console.log(this.length);
}
var obj = {
    length: 5,
    method: function(fn) {
        fn();
        arguments[0]();
    }
}
obj.method(fn, 1)
// 输出 10 2
// 第一次输出10很好理解,fn无人调用,所以this指向window
// 第二个输出2是因为,arguments是一个类数组对象
// arguments[0]()相当于arguments这个对象调用了fn函数,因此输出2

bind改变this指向的例子: 

var name = 'LK'
function Person (name) {
  this.name = name
  this.sayName = function () {
    setTimeout(function () {
      console.log(this.name)
    }, 50);
  }
}
var person = new Person ('lk')
person.sayName()    // 输出'LK'
// setTimeout定时函数相当于window.setTimeout(),由window这个全局对象调用,因此this指向window

// 使用bind
var name = 'LK'
function Person (name) {
  this.name = name
  this.sayName = function () {
    setTimeout(function () {
      console.log(this.name)
    }.bind(this), 50);
  }
}
var person = new Person ('lk')
person.sayName()    // 输出'lk'

JS中this关键字详解 - 沙沙起 - 博客园


call 、 apply和bind

 call()和apply()方法都是函数对象的方法,需要通过函数对象来调用

 当函数调用call()和apply()都会调用函数执行

function f(){
    console.log('LKing')
} 

f()        // LKing
f.call()   // LKing
f.apply()  // LKing

 在调用call()和apply()可以将一个对象指定为第一个参数,修改this的指向

obj = {}

function f(){
    console.log(this);
}

f()            // Window
f.call(obj)    // Object
f.apply(obj)   // Object

 call和apply的小不同

obj = {}

function f(a, b){
    console.log(a);
    console.log(b);
}

f()
f.call(obj, 11, 22)
f.apply(obj, [11, 22])

// call()方法可以将实参在对象之后依次传递
// apply()方法需要将实参封装到一个数组中同一传递

bind与call和apply作用类似,不同的是bind()方法返回的是一个函数 

<script>
 	var name = 'Lily',
 		age = 13
 	var obj = {
 		name: 'Sam',
 		objAge: this.age,
 		myFunc: function (param1, param2) {
 			console.log(this.name + ' ' + this.objAge + ' ' + this.age + ' ' + param1 + ' '  + param2)
 			// Amy 20 21 1 2
 		}
 	}
 	var db = {
 		name: 'Amy',
 		age: 21,
 		objAge: 20
 	}
 	obj.myFunc.call(db, '1', '2') // db为this要指向的对象,后面传入的是参数列表,参数可以是任意类型,当第一个参数为null、undefined的时候,默认指向window;
    obj.myFunc.apply(db, [1, 2]) // db为this要指向的对象,参数必须放在一个数组里面;
 	obj.myFunc.bind(db, '1', '2')() // db为this要指向的对象,返回的是一个新函数,必须调用才会去执行。
</script>

手写apply(call同理)

Function.prototype.myApply = function(context, args) {
  // 如果不传值则默认为window
  context = context || window
  args = args ? args : []
  // 给context新增一个独一无二属性以免覆盖原有属性
  const key = Symbol()
  context[key] = this
  // 通过隐式绑定的方法调用函数
  const res = context[key](...args)
  // 删除添加的属性
  delete context[key]
  // 返回函数调用的结果
  return res
}

手写bind

// 简单版
Function.prototype.myBind = function(context, ...args) {
  const self = this
  context = context || window
  args = args ? args : []
  return function () {
    return self.apply(context, ...args, ...arguments)
  }
}

// 复杂版
Function.prototype.myBind = function(context, ...args) {
  // 判断调用bind是否为一个函数
  if (typeof(this) !== 'function') {
    throw new Error('not a function!')
  }
  // this表示调用这个函数的对象
  const self = this
  context = context || window
  return function newFn() {
    // 如果bind的返回值用new去调用
    if (this instanceof newFn) {
      return new self(...args, ...arguments)
    }
    return self.apply(context, [...args, ...arguments])
  }
}


闭包

 定义

有权访问另一个函数作用域中的变量的函数;一般情况下就是在一个函数中包含另一个函数

function func () {
  let closure = '闭包'
  function inner () {
    console.log(closure)
  }
  return inner
}

let f = func()
f()    // '闭包'
f()    // 同上,说明变量closure并没有被销毁,一直存在内存中

 原理:

 闭包实现的原理,其实是利用了作用域链的特性,作用域链就是在当前执行环境下访问某个变量时,如果不存在就一直向外层寻找,最终寻找到最外层就是全局作用域,这样就形成了一个链条

function func () {
  let num = 0
  function add () {
    return ++num
  }
  return add
}

func()()        // 1
func()()        // 1
let f = func()
f()             // 1
f()             // 2

 宏任务和微任务

 宏任务(macrotask)和微任务(microtask)是异步任务的两种分类

 在挂起任务时,JS引擎会将所有异步任务分到这两个队列中,首先在marcotask的队列中取出第一个任务,执行完毕后取出microtask队列中所有任务顺序执行;之后再取macrotask任务,周而复始,直到两个队列的任务都取完


 对调两个变量

var a = 1
var b = 2

// 临时变量法
var c = b  // 临时变量
b = a
a = c

// 加减法
a = a + b
b = a - b
a = a - b

// 数组法
a = [a, b]
b = a[0]
a = a[1]

// 对象法
a = {a: b, b: a}
b = a.b
a = a.a

// 数组运算法
a = [b, b = a][0]

// 按位异或法
a = a ^ b
b = a ^ b
a = a ^ b

// 解构赋值法
[a, b] = [b, a]

AJAX的五个步骤

// 创建XMLHttpRequest异步对象
var xhr = new XMLHttpRequest()

// 设置回调函数
xhr.onreadystatechange = callback

// 使用open方法与服务器建立连接
// get
xhr.open('get', 'test.php', true)
// post
// xhr.open('post', 'test.php', true)
// xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')

// 向服务器发送数据
// get不需要传递参数,post需要
xhr.send(null)

// 在回调函数中针对不同的响应状态进行处理
function callback () {
  // 状态码
  // 0: 请求未初始化
  // 1: 服务器已建立连接
  // 2: 请求已接收
  // 3: 请求处理中
  // 4:请求已经完成
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      var res = xhr.responseText
      res = JSON.parse(res)
    }
  }
}

fetch 

fetch的功能与XMLHttpRequest基本相同,但是有三个主要差异

  1. fetch使用Promise,不使用回调函数
  2. fetch采用模块化设计,API分散在多个对象上(Request对象,Response对象,Header对象),更合理一些,相比之下,XMLHttpRequest的API设计并不是很好,输入输出状态都在同一个接口管理
  3. fetch通过数据流(Stream对象)处理数据,可以分块读取,有利于提高网站性能表现

fetch()接受一个URL字符串作为参数,默认向网站发出GET请求,返回一个Promise对象

fetch('https://api.github.com/users/ruanyf')
  .then(response => response.json())
  .then(json => console.log(json))
  .catch(err => console.log('Request Failed', err)); 

// async函数写法
async function getJSON() {
  let url = 'https://api.github.com/users/ruanyf';
  try {
    let response = await fetch(url);
    return await response.json();
  } catch (error) {
    console.log('Request Failed', error);
  }
}

fetch()接收到的response是一个Stream对象,response.json()是一个异步操作,取出所有内容,将其转化为JSON对象

fetch()发出请求后,只有两种情况会报错:网络错误或者无法连接,所以要通过Response.status属性,得到HTTP回应的真是状态码,判断是否请求成功

async function fetchText() {
  let response = await fetch('/readme.txt');
  if (response.status >= 200 && response.status < 300) {
    return await response.text();
  } else {
    throw new Error(response.statusText);
  }
}

读取方法:

  • response.text():得到文本字符串。
  • response.json():得到 JSON 对象。
  • response.blob():得到二进制 Blob 对象。
  • response.formData():得到 FormData 表单对象。
  • response.arrayBuffer():得到二进制 ArrayBuffer 对象。

 Stream对象只能读取一次,读取完就没了,所以说五个读取方法,只能使用一次,response.clone()对象就可以复制一份Reaponse对象


Array 

【Array.from()】

 可将两类对象转化为真正的数组:类数组对象和可遍历对象。该方法还可以接受第二个参数,作用类似于数组的map方法,用来对每一个元素进行处理,将处理后的值放入数组中

【copyWithin()】 

数组实例方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组,该方法会修改当前数组,有三个参数

  • target(必需):从该位置开始替换数据,如果为负值,则表示倒数
  • start(可选):从该位置开始读取数据,默认为0,可为负值
  • end(可选):到该位置前停止读取数据,默认等于数组的长度,可为负值 

 【find()和findIndex()】

 find方法,用于找出第一个符合条件的数组成员,它的参数是一个回调函数,findIndex就是找出第一个符合条件的数组成员的下标

【entries(), keys(),和values()】 

用于遍历数组,它们都会返回一个遍历器对象,可以用for...of循环遍历,keys是对键名的遍历,values是对键值的遍历,entries是对键值对的遍历 


深拷贝 

function isObject(obj) {
  return Object.prototype.toString.call(obj) === '[object Object]' || Array.isArray(obj)
}
function deepClone(obj, hash=new Map()) {
  if (!isObject(obj)) return obj
  if (hash.has(obj)) return hash.get(obj)
  let objClone = Array.isArray(obj) ? [] : {}
  hash.set(obj, objClone)
  for (let key in obj) {
    if (isObject(obj[key])) {
      objClone[key] = deepClone(obj[key], hash)
    } else {
      objClone[key] = obj[key]
    }
  }
  return objClone
}

ES6 


let和const

  • 没有变量提升
  • 存在块级作用域
  • 不能重复命名
  • const声明后不能修改 

与var相比的作用 

  1. for循环

  2. 不会造成全局污染 


Object.is()

NaN === NaN // false
Object.is(NaN, NaN) // true

-0 === +0 // true
Object.is(-0, +0) // false

class-extends-super

class属于一种语法糖,让代码看起来更像是面向对象

class Point {
  constructor (x, y) {
    this.x = x
    this.y = y
  }
  toString () {
    return '(' + this.x + ',' + this.y + ')'
  }
}

// 等同于
function Point(x, y) {
  this.x = x
  this.y = y
}
Point.prototype.toString = function () {
  return '(' + this.x + ',' + this.y + ')'
}

constructor是类的构造函数,一个类必须有一个构造函数,如果没有显式的,一个默认的constructor方法也会被默认添加,一般constructor方法返回实例对象this,但是也可以指定返回一个全新的对象,让返回的实例对象不是该类的实例

在ES6中使用关键字extends实现继承

class Point {
  constructor (x, y) {
    this.x = x
    this.y = y
  }
  toString () {
    return '(' + this.x + ',' + this.y + ')'
  }
}
class ColorPoint extends Point {
  constructor (x, y, color) {
    super(x, y)  // 调用父类的constructo(x, y)
    this.color = color
  }
  toString () {
    return this.color + ' ' + super.toString() // 调用父元素的toString()
  }
}

在子类的constructor中必须调用super方法,因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,而super就代表着父类的构造函数,其返回的是子类的实例,即super内部的this指向子类。super.toString()相当于Point.prototype.toString()

Object.getPrototypeOf()方法可以用来从子类上获取父类,可以用这个方法检查一个类是否继承另一个类

关于super: 

class A {
  constructor() {
    this.x = 1;
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
    super.x = 3;
    console.log(super.x); // undefined
    console.log(this.x); // 3
  }
}

let b = new B();

由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性也会变成子类实例的属性 

super总结:

  • 可以当作函数使用,可以当作对象使用
  • 当作函数使用,代表的是父类的构造函数
  • 当作对象时,在普通方法中,指向父类的原型对象
  • 当作对象时,在静态方法中,指向父类
  • 当作对象时,方法内部的this指向当前子类实例 

箭头函数 

var f = v => v

// 等同于
var f = function (v) {
  return v
}

 箭头函数格式问题:

// 报错
let getObj = id => {id: id, name: 'LK'}
// 正确
let getObj = id => ({id: id, name: 'LK'})

箭头函数的使用问题:

  1. 箭头函数没有自己的this对象 
  2. 不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出错误
  3. 不可以使用arguments对象,该对象在函数体里不存在,如果要用,可以用(...变量)名代替
  4. 不可以使用yield命令,因此箭头函数不能用作Generator函数

箭头函数this指向问题: 

对于普通函数来说,内部的this指向函数运行时所在的对象,但是这一点对箭头函数不成立。它没有自己的this对象,内部的this,就是定义时上层作用域中的this。也就是说,箭头函数内部的this指向是固定的,相比之下,普通函数的this指向是可变的 

function foo () {
  return () => {
    return () => {
      return () => {
        console.log(this.id)
      }
    }
  }
}
var f = foo.call({id: 1})

var t1 = f.call({id: 2})()() // 1
var t2 = f().call({id: 3})() // 1
var t3 = f()().call({id: 4}) // 1

所有的内层函数都是箭头函数,都没有自己的this,它们的this其实都是最外层foo函数的this。

箭头函数不适用的场景: 

第一个场所是定义对象的方法,且该方法内部包含this

var obj = {
  func: () => {
    console.log(this)
  }
}
obj.func() // window

第二个场景是需要动态this的时候,也不应该使用箭头函数


Symbol 

ES6新引进的原始数据类型,表示独一无二的值,Symbol函数前面不能使用new命令,否则会报错,这是因为生成的Symbol是一个原始类型的值,不是对象

let s1 = Symbol()
let s2 = Symbol()
s1 === s2        // false

Symbol值作为对象的属性名时,该属性还是公共属性,不是私有属性,所以还是可以用[]访问到该属性值,但是不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyName()返回,但是有一个Object.getOwnPropertySymbols方法,可以获取对象的所有Symbol属性名

【Symbol.for()】

let s1 = Symbol.for('foo')
let s2 = Symbol.for('foo')
s1 === s2                 // true

Symbol()和Symbol.for()都会生成新的Symbol,区别就是,后者会被登记再全局变量中供搜索,前者不会,因此调用Symbol.for()不会每次调用就返回一个新的Symbol类型的值,而是会检查给定的key时候已经存在,如果不存在才会返回一个新的 

【Symbol.keyFor()】 

let s1 = Symbol.for('foo')
Symbol.keyFor(s1)    // 'foo'

let s2 = Symbol('foo')
Symbol.keyFor(s2)    // undefined

Symbol.keyFor()方法返回一个已登记的Symbol类型值的key


Map 

map初始化 

let map = new Map([['a', 1], ['b', 2]])
// 表示有两个键值对,键分别时a和b,值分别是1和2

Iterator(遍历器)

遍历器是一个接口,为各种不同的数据结构提供一个统一的访问机制,任何数据结构只要部署了iterator接口就可以完成遍历操作(for...of) 

Iterator的遍历过程: 

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质是一个指针对象 
  2. 第一次调用指针对象的next方法,可以将指针指向数据的第一个成员
  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员
  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置

默认的Iterator接口部署在数据结构的Symbol.iterator属性 

手动添加Symbol.iterator属性 


Generator(生成器)

Generator函数是ES6提供的一种异步编程解决方案 

执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数 

形式上Generator函数是一个普通函数,但是有两个特征,一是function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同内部状态

yield的格式:

如果yield表达式用在另一个表达式之中,必须放在圆括号里面 

next函数的参数: 

yield表达式本身没有返回值,next方法可以带一个参数,该参数就会被当作是上一个yield表达式的返回值

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

Generator函数的作用 :

1. 可以给无法遍历的对象部署Iterator接口

function* iterEntries(obj) {
  let keys = Object.keys(obj);
  for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
  }
}

let myObj = { foo: 3, bar: 7 };

for (let [key, value] of iterEntries(myObj)) {
  console.log(key, value);
}

// foo 3
// bar 7

2. 异步操作的同步化表达

Generator函数的暂停执行的效果,意味着可以把异步操作写在yield表达式里面,等到调用next方法后再往后执行 

function* loadUI() {
  showLoadingScreen();
  yield loadUIDataAsynchronously();
  hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next()

// 卸载UI
loader.next()

Promise 

Promise对象是一个构造函数,用来生成promise对象;Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,它们也是函数,由Javascript引擎提供,不需要部署

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

resolve函数的作用是,将Promise对象的状态从“未完成”变成“成功”(pending到resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数同理,只是将状态变为“失败”

Promise实例生成后,可以用then方法分别指定resolved状态和rejected状态的回调函数

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

【Promise.prototype.then()】 

then方法的作用是为Promise实例添加状态该表时的回调函数,then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的;then方法返回的是一个新的Promise实例,因此可以在then方法后面再调用另一个then方法

getJSON("/posts.json").then(function(json) {
  return json.post;
}).then(function(post) {
  // ...
});

【Promise.prototype.catch()】

catch方法是.then(null, rejection)或.then(undefined, rejection) 的别名,用于指定发生错误时的回调函数

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // 处理 getJSON 和 前一个回调函数运行时发生的错误
  console.log('发生错误!', error);
});

【Promise.prototype.finally】

finally()方法用于不管Promise对象状态如何,都会执行的操作,实现如下

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

【Promise.all()】

Promise.all()方法用于将多个Promise实例,包装成一个新的Promise实例,接受一个数组作为参数,p1、p2和p3都是Promise实例,参数也可以不是数组,但是必须具有Iterator接口,且返回的每个成员都是Promise实例 

const p = Promise.all([p1, p2, p3]);

p的状态分情况决定:

  1. 只有全部Promise实例状态都变为resolved,p的状态才会变成resolved。此时所有Promise实例的返回值组成一个数组,传递给p的回调函数
  2. 只要一个Promise实例被rejected,p的状态就会变成rejected,此时第一个rejected的实例的返回值,会传递给p的回调函数

(Promise.any()方法与之相反,下面是手写Promise.any) 

MyPromise.any = function(promises){
  return new Promise((resolve,reject)=>{
    promises = Array.isArray(promises) ? promises : []
    let len = promises.length
    // 用于收集所有 reject 
    let errs = []
    // 如果传入的是一个空数组,那么就直接返回 AggregateError
    if(len === 0) return reject(new AggregateError('All promises were rejected'))
    promises.forEach((promise)=>{
      promise.then(value=>{
        resolve(value)
      },err=>{
        len--
        errs.push(err)
        if(len === 0){
          reject(new AggregateError(errs))
        }
      })
    })
  })
}

【Promise.race()】 

const p = Promise.race([p1, p2, p3]);

 只要p1、p2、p3之中有一个实例率先改变状态,p的状态就会跟着改

【Promise.resolve()】 

可以将现有对象转化为Promise对象 

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))


async 

async函数返回一个Promise对象

async函数内部return语句返回的值,会成为then方法回调函数的参数

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

async函数内部抛出错误,会导致返回的Promise对象变为reject状态,抛出的错误对象会被catch方法回调函数收到

async function f() {
  throw new Error('出错了');
}

f().then(
  v => console.log('resolve', v),
  e => console.log('reject', e)
)
//reject Error: 出错了

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。

async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world

另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。

async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world

DOM

  •  DOM,Document Object Model,文档对象模型
  • JS中通过DOM来对HTML文档进行操作,只要理解了DOM就可以随心所欲的操作WEB页面
  • 文档,表示的就是整个HTML页面文档
  • 对象,表示将网页中的每一个部分都转化为了一个对象
  • 模型,使用模型来表示对象之间的关系,这样方便我们获取对象(DOM树)

节点

 节点:Node,构成HTML文档最基本的单元

 常用的节点分为四类:

  • 文档节点:整个HTML文档
  • 元素节点:HTML文档中的HTML标签
  • 属性节点:元素的属性
  • 文本节点:HTML标签中的文本内容

  节点属性(文本节点的值用的比较多而已)

nodeNamenodeTypenodeValue
文档节点#document9null
元素节点标签名1null
属性节点属性名2属性值
文本节点#text3文本内容

 浏览器已经为我们提供了文档节点document对象,这个对象是window属性


事件

 事件,就是用户和浏览器之间的交互行为(比如:点击按钮、鼠标移动、关闭窗口......)

 HTML DOM Event对象:HTML DOM Event 对象

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn">i am a button</button>
    <script>
        // 获取按钮对象
        var btn = document.getElementById('btn')
        // 可以为按钮的对应事件来绑定处理函数的形式来相应事件
        // 绑定一个单击事件
        // 像这种为单击事件绑定的函数,称之为单击响应函数
        btn.onclick = function(){
            alert('hello')
        }
    </script>
</body>
</html>

鼠标移动事件onmousemove中,鼠标的坐标属性:

clientX和clientY:用于获取鼠标在当前可见窗口的坐标

pageX和pageY:用于获取鼠标相对于整个页面的坐标


 DOM查询

 通过documen对象调用获取元素节点

  • getElementById():通过id属性获取一个元素节点对象
  • getElementsByTagName():通过标签名获取一组元素节点对象
  • getElementsByName():通过name属性获取一组元素节点对象
  • getElementsByClassName():通过class属性获取一组元素节点对象
  • querySelector():需要一个选择器的字符串参数,可以根据CSS选择器查询一个元素节点对象
  • querySelectorAll():与querySelector()类似,不同的是会返回一组元素节点对象

 通过具体的元素节点调用

  • getElementsByTagName():-方法,返回当前节点的指定标签后代节点
  • childNodes:-属性,表示当前节点的所有子节点
  • firstChild:-属性,表示当前节点的第一个子节点
  • lastChild:-属性,表示当前节点的最后一个子节点
  • parentNode:-属性,表示当前节点的父节点
  • previousSibling:-属性,表示当前节点的前一个兄弟节点
  • nextSibling:-属性,表示当前节点的后一个兄弟节点

 childNode属性会获取包括文本节点在内的所有节点

 而children属性只会获得元素节点


DOM增删改

 创建一个元素节点对象,需要标签名作为参数

document.createElement()

 创建一个文本节点对象,需要一个文本内容作为参数

document.createTextNode()

 向一个父节点中添加一个新的子节点

父节点.appendChild(子节点)

 在指定的子节点前插入新的子节点

父节点.insertBefore(新节点, 旧节点)

 将新的节点替代指定的子节点

父节点.replaceChild(新节点, 旧节点)

 删除指定的子节点

父节点.removeChild(子节点)

 父节点的获取方式:子节点.parentNode


DOM与CSS

 修改内联样式的语法如下(样式名中含有-,如background-color,需要将样式名修改成驼峰命名法):

元素.style.样式名 = 样式值

 获取当前元素正在显示的样式

元素.currentStyle.样式名    // 只有IE可用

// 其他浏览器的方法
// 第一个参数为要获取样式的元素
// 第二个参数可以传递一个为元素,一般情况下为null
// 会返回一个对象,对象中封装了当前元素对应的样式
getcomputedStyle()

事件冒泡

 事件的冒泡(Bubble):所谓的冒泡就是事件的向上传导,当后代元素上的事件被触发时,其祖先元素的相同事件也会被触发

<body>
  <div>
    <span></span>
  </div>
</body>

// 如果分别给body、div和span绑定一个点击事件
// 点击span后,会最先触发span的点击事件
// 然后再是div,最后是body

在开发中,大部分的冒泡都是有用的,如果不希望事件冒泡则可以用事件对象来取消冒泡

event.cancelBubble = true

 事件的委派:将事件统一绑定给元素的共同祖先元素,这样后代元素上的事件触发时,会一直冒泡到祖先元素,从而通过祖先元素的响应函数来处理


事件的绑定

// 事件的绑定
// 假设btn是获取到的按钮元素

btn.onclick = function() {
    // 响应函数一
}

btn.onclick = function() {
    // 响应函数2
}

// 上述绑定方式会使得后面的响应函数覆盖前面的响应函数

 addEventListener()

         -通过这个方法也可以为元素绑定响应函数

         -参数:

                1. 事件的字符串,不要on(如:click)

                2. 回调函数,当事件触发时函数会被调用

                3. 是否在捕获阶段触发事件,需要一个布尔值,一般都传false

     事件触发后,会按照绑定顺序执行

btn.addEventListener(
  'click',
  function () {
    // 我是一个回调函数
  },
  false
);

btn.addEventListener(
  'click',
  function () {
    // 我是一个回调函数
  },
  false
);

  事件的传播分为三个阶段:

        1、捕获阶段 

                -从最外层的祖先元素,向目标元素进行事件的捕获,但是默认不会触发事件(false)

        2、目标阶段

                -事件捕获到目标元素,捕获结束开始在目标元素上触发事件

        3、冒泡阶段

                -事件从目标元素向它的祖先元素传递,依次触发祖先元素上的事件


鼠标滚轮事件

 事件对象onwheel

// 案例为滚动滑轮改变div的height
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>鼠标滚轮</title>
  </head>
  <body>
    <div id="box"></div>
    <script>
      let box = document.getElementById('box');
      box.onwheel = function (event) {
        // console.log();
        box.style.height = box.clientHeight + event.deltaY / 10 + 'px';

        // 取消默认行为
        event.preventDefault();
        // return false
      };
    </script>
  </body>
  <style>
    #box {
      height: 200px;
      width: 200px;
      background-color: lightsalmon;
      transition: 0.5s;
    }
  </style>
</html>

键盘事件

 键盘事件一般会给绑定一些可以获取到焦点的对象或者是document(如:input)

 键盘事件通常提供以下三个属性,可以用来判断是否对应的键是否有事件发生(用来判断同时按两个键):

  • altKey
  • ctrlKey
  • shiftKey

 docume.write和innerHTML 


clientHeight等 

网页可见区域高:documen.body. clientHeight

网页正文全文高:documen.body. scrollHei

网页可见区域高(包括边线的高):documen.body. offsetHei

网页被卷去的高:documen.body. scrollTop


BOM

  •  Browser Object Model浏览器对象模型
  • BOM可以使我们通过JS来操作浏览器
  • BOM提供了一组对象用来完成对浏览器的操作

BOM对象

 Window

        - 代表的是整个浏览器的窗口,同时window也是网页中的全局对象

 Navigator

        - 代表的是当前浏览器的信息,通过该对象可以来识别不同的浏览器

          由于历史原因,Navigator对象中的大部分属性都已经不能帮助我们识别浏览器了

 Location

        - 代表当前浏览器地址栏的信息,通过Location可以获取地址栏信息,或者跳转页面

        - assign()

                用来跳转到其他页面,会产生历史记录

        - reload()

                重新加载当前页面,如同刷新,若传入一个true参数,可以强制清空缓存

        - replace()

                用新的页面替换当前页面,不产生历史记录,无法回退

 History

        - 代表浏览器的历史记录,可以通过该对象来操作浏览器的历史记录,

          由于隐私问题,该对象不能具体获取到具体的历史记录,只能操作浏览器向前或向后翻页

          而且该操作只在当此访问有效

        - length:

                属性,可以获取到当前访问的链接数量

        - back()

                可以回退到上一个页面,作用和浏览器的回退按钮一样

        - forward()

                可以跳转到下一个页面,作用和浏览器的前进按钮一样

        - go()

                可以跳转到指定页面,需要一个整数作为参数,正数表示向前跳转n个页面,负数相反

 Screen

        - 代表用户的屏幕信息,通过该对象可以获取到用户的显示器的相关信息


window定时器

 setInterval()

  • 定时调用
  • 可以将一个函数,每隔一段时间执行一次
  • 参数
  1. 回调函数,该函数会每隔一段时间被调用一次
  2. 每次调用的时间间隔,单位是毫秒
  •  返回值:返回一个Number类型的数据,作为定时器的唯一标识

 clearInterval()

  • 用于关闭定时器
var timer = setInterval(function () {

  // 回调函数......

  if (/*关闭定时器的条件*/) {

    // 关闭定时器
    clearInterval(timer);

  }
}, 1000);

 setTimeout()

  •  延时调用
  • 一段时间后调用函数,且只调用一次

 clearTimeout()

  •  关闭一个延时调用

CSS

 单位

 宽度单位:

  • %:相对单位,相对于父元素的大小
  • px:对于父元素来说是绝对单位,相对于显示屏分辨率来说是相对单位
  • vw:viewport width,相对于视口的相对单位(vh同理)

 HTML代码

 

 CSS代码

 

 运行结果

 字体单位:

  • rem:r表示root,相对于根元素的字体大小
  • em:相对于父元素的字体大小
  • %:相对于父元素的字体大小

 

 HTML代码

 

 CSS代码

 

 运行结果


渐变

 渐变是图片的一种,需要通过background-image来设置

1. 线性渐变:

 颜色沿着一条直线发生变化

 使用函数 linear-gradient()

 background-image:linear-gradient(direction, color-stop1, color-stop2, ...) 

参数作用
direction指定渐变的方向
color-stop指定渐变的起止颜色

-线性渐变的开头,我们可以指定一个方向

        to top / left / bottom / right

        xxx deg (deg表示度数)

        xxx turn (turn表示圈)

-渐变可以指定多个颜色,默认平均分配

        可以手动分配颜色渐变分布,如下:

        linear-gradient(red 50px, yellow 150px)

        red 50px表示0-50px的位置都是red

        yellow 150px表示从150px开始剩下的位置都是yellow

 重复的线性渐变

 

 

 2. 径向渐变

 放射性的渐变效果

 radial-gradient(大小 at 位置,颜色 位置,颜色 位置,...)


 多行省略 


清除浮动 

浮动的问题:

        设置浮动后,会造成浮动塌陷,因为设置浮动后,元素会脱离文档流,如果包含该元素的块没有设置高度的话,此时包含块的高度会塌陷 

设置浮动前 

设置浮动后

 清除浮动的方法:

        1.在浮动元素后增加一个带有clear:both的元素

        2.将父元素设置成BFC,增加属性overflow:hidden

        3.与第一种方法类似,给父元素设置一个伪元素,赋予clear的属性


外边距塌陷 

两个在正常流中相邻(兄弟或者父子关系)的块级元素的外边距。组合在一起变成单个外边距

外边距塌陷不是bug,它的设计初衷是为了段落垂直方向之间的空隙

【什么时候会塌陷】 

  1. 垂直方向,不是水平方向
  2. 块级元素,不是行内元素,也不是行内块元素

【如何计算外边距】 

  • 正数&正数,取最大的正数
  • 负数&负数,取最小的负数
  • 正数&负数,取相加之和  

【如何解决】  

  1. 绝对定位

        父元素设置相对定位,子元素设置绝对定位,子元素设置外边距就不会塌陷了

  2. 行内块级元素

        将子元素设置成行内块级元素,因为外边距塌陷只存在于块级元素中

  3.  相对定位

        给子元素设置相对定位,然后设置相对于原来位置的偏移

  4. 浮动

  5. BFC 

        将父元素设置成BFC

  6. 内边距

        给父元素设置内边距

  7. 边框

        给父元素设置边框

  8. 空元素

        给相邻元素之间设置空元素,要注意的是,空元素的上下外边距会重合 


BFC 

Block Formatting Context,块级格式化上下文,它是一个独立的渲染区域,它规定了内部Block-level Box如何布局,并且与这个区域外部毫不相干

【BFC的布局规则】

  • 内部的Box会在垂直方向,一个接着一个地放置
  • Box垂直方向的距离由margin决定,属于同一个BFC的两个Box的margin会发生塌陷
  • BFC中子元素的左外边距会紧挨着父元素的左内边距,即使存在浮动也是如此
  •  BFC区域不会与float box发生重叠
  • BFC就是页面上一个隔离的独立容器,容器里面的子元素不会影响到外面的元素
  • 计算BFC的高度时,浮动元素也参与计算

【如何创建BFC】

  •  根元素(有说html也有说是body)
  • 浮动元素:float除了none以外的值
  • 绝对定位元素:position为absolute或者fixed
  • display为inline-block、table-cells、flex
  • overflow除了visible以外的值(hidden、auto、scroll)

line-height 

行高就是两个基线之间的距离

【行距】 

  行距 = line-height - font-size,半行距就是行距除以二,上下半行距的大小一定相同,这就是line-height能实现垂直居中的原因

【line-height的值】 

  • normal:浏览器默认的值
  • 百分数:font-size * 百分数
  • 数字:line-height = 数字 * font-size

【行框属性】 

  • 内边距外边距和边框不会影响到行框的高度,即不影响行高 
  • 行内元素的边框边界是由font-size而不是line-height控制的 

vertical-align 

vertical-align用来设置垂直对齐方式,所有对齐元素都会影响行高,只能应用在行内元素

【vertical-align的值】 

  • baseline:默认,元素放置在父元素的基线上
  • sub:垂直对齐文本的下标
  • super:垂直对齐文本的上标
  • top:把元素的顶端与行中最高元素的顶端对齐
  • text-top:把元素的顶端与父元素字体的顶端对齐
  • middle:元素的中垂点与父元素的基线加1/2父元素中字母x的最高对齐
  • bottom:把对齐的子元素的底端与行框底端对齐
  • text-bottom:把元素的底端与父元素内容区域的底端对齐
  • (+-n)px:元素相对于基线上下偏移npx
  • x%:相对于元素的line-height值
  • inherit:从父元素继承vertical-align属性值

【行盒baseline的移动】 

  行盒的baseline受该行所有元素的影响。我们假设有个元素以这种方式对齐(相对于自身baseline对齐),行盒的baseline就不得不移动。因为大多数竖直对齐(除了top和bottom)都是相对其baseline的,导致该行所有其他元素也跟着调整位置 

  • 如果一行有个人元素横跨了整个高度,vertical-align对它不起作用了,它顶部之上和底部之下已经没有能提供它移动的空间了。为了满足其他行盒baseline的对齐关系,行盒baseline就是不得不移动了。矮方块具有vertical-align:baseline,左边高方块是text-bottom对齐,右边是text-top对齐,可以发现baseline带着矮盒子一起跳上去了
  • 一行里面放两个大元素,竖直对齐它们会移动baseline到满足它们对齐方式的位置,然后行盒的高度也会调整。添上第三个元素,其对齐方式不会让它超出行盒的边界的话,既不影响行盒的高度也不影响baseline的位置(中图)。如果它超出了行盒的边界,行盒的高度和baseline就会再次调整,这种情况下我们最初的两个方块被推下去了

垂直居中 

垂直居中一直都是一个难点,对于垂直居中,水平居中显得尤为简单 

【水平居中】 

  • 行内元素,对其父元素应用text-align:center
  • 块级元素,对其自身margin:auto  

接下来是垂直居中的解决方案

【基于绝对定位】 

利用绝对定位,相对于父元素的宽高偏移50%,然后再用translate()变形函数,以自身的宽高为基准偏移50%,核心代码如下

 缺点:

  • 有时候绝对定位对整个布局影响太过剧烈
  • 如果需要居中的元素已经在高度上超过了视口,那它的顶部会被视口裁剪掉
  • 在某些浏览器中,这个方法可能会导致元素的显示有一些模糊,因为元素可能会被放置在半个像素上

【基于视口单位】 

利用视口单位,vw是与视口宽度相关的,vh是与视口高度相关的,1vw就是视口宽度的1%,对于水平居中,至于需要让左右两边的外边距为auto,然后再让上外边距为50vh,即视口高度的50%,然后再用translateY来相对自身的高度移动,核心代码如下

 缺点:只适用于视口中居中的场景

【基于Flexbox】 

给父元素设置display:flex,再给父元素本身设置margin:auto即可,代码如下 

 同时flexbox也可以让可以让匿名容器(即没有被标签包裹的文本节点),代码如下

 


弹性布局(display:flex)

Flexbox是flexible box的简称,是CSS3新的布局模式

基本概念

采用Flex布局的元素,称为flex容器(flex container),简称“容器” ,它的所有子元素自动成为容器成为,成为flex项目(flex item),简称“项目”

容器属性

【flex-direction】

  决定主轴(main axis)的方向

  • row(默认): 主轴为水平方向,起点在左端
  • row-reverse:主轴为水平方向,起点在右端
  • column:主轴为垂直方向,起点在上沿
  • column-reverse:主轴为垂直方向,起点在下沿

【flex-wrap】

  决定是否换行

  • nowrap(默认):不换行
  • wrap:换行,第一行在上方
  • wrap-reverse:换行,第一行在下方 

【flex-flow】 

  flex-direction和flex-wrap的简写,默认row和nowrap 

【justify-content】 

  定义了项目在主轴上的对齐方式

  • flex-start(默认):左对齐
  • flex-end:右对齐
  • center:居中
  • space-between:两端对齐,项目之间的间隔都相等
  • space-around:每个项目之间的间隔相等。所以项目之间的间隔比项目与边框的间隔大一倍 

 【align-items】

   定义了在交叉轴上的对齐方式,对齐方式与交叉轴有关

  • flex-start:交叉轴的起点对齐
  •  flex-end:交叉轴的终点对齐
  • center:交叉轴的中点对齐
  • baseline:项目的第一行文字基线对齐
  • stretch(默认):如果项目未设置高度或设为auto,将占满整个容器的高度

【align-content】

  定义 多根轴线的对齐方式,如果项目中只有一个轴线,该属性就不起作用。当你不给项目设置高度但是容器设置align-content不为stretch,同一轴线上的项目高度将等于项目中高度最高的项目

  • flex-start:交叉轴的起点对齐
  • flex-end:交叉轴的终点对齐
  • center:交叉轴的中点对齐
  • space-between:交叉轴的两端对齐,轴线之间的间隔平均分布
  • space-around:每根轴线之间的间隔相等
  • stretch(默认):轴线占满整个交叉轴

项目属性

【order】 

  数值越小,排列越靠前,默认是0,可以是负值 

【flex-grow】

  默认是0,即如果空间有剩余,也不放大,可以是小数,按比例占据剩余空间

【flex-shrink】 

  默认值是1,即如果空间不足将等比例缩小,如果有一个项目的值为0,其项目为1,当空间不足时,该项目不缩小

 【flex-basis】

   默认值为auto,浏览器根据此属性检查主轴是否有多余空间,权重高于width

  • npx:分配多余空间之前项目占据主轴npx,如果空间不足则缩小 

 【flex】 

   flex-grow、flex-shrink和flex-basis三个属性的缩写,默认值为:0 1 auto

   flex:none(0 0 auto)表示不放大也不缩小

   flex:auto(1 1 auto)表示放大也缩小

   flex:1(1 1 0%)

 【align-self】 

   align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性 


CSS优先级

  1. important无条件优先
  2. 内联样式 1000
  3. id选择器 100
  4. class、伪类、属性 10
  5. 标签、伪元素 1 

CSS选择器 

element>elementdiv > p选择父元素是<div>的所有<p>元素
element+elementdiv + p选择紧跟着<div>元素的首个<p>元素
element1~element2p ~ ul选择前面有<p>元素的每个<ul>元素

包含块

当设置某些元素的属性值(width、heigh、padding等)使用到了%,此时的百分数是相对于元素包含块的宽度

包含块确定规则:

1、根元素所在的包含块叫初始包含块initial containingblock。对于连续媒体设备(continuous media),初始包含块的大小等于视口(viewport)的大小,基点在视口的左上角;对于分页媒体(page media),初始包含块是页面区域。初始包含块的direction属性与根元素相同

2、对于其他元素,如果position属性是relative或static,他的包含块是由最近的祖先块盒容器的内容区域(width和height属性确定的区域)创建的

3、如果一个元素的position属性为fixed,他的包含块由视口或者页面区域创建的

4、如果一个元素的position为absolute,它的包含块由最近的position不为static的祖先元素创建,具体创建方式如下:

        a、如果创建包含块的祖先元素是行内元素,包含块的范围是这个祖先元素中的第一个和最后一个行内盒的padding box围起来的区域

        b、如果这个元素不是行内元素,包含块的范围是这个祖先元素的内边距+width区域,如果没有找到这样的祖先元素,这个绝对定位的元素包含块为初始包含块

  


回流和重绘 

回流: 

当我们操作DOM时,使其结构发生改变,从而影响整个布局,这个过程就会发生回流

  1. 当元素的width、height、margin、padding、文字图片等发生变化
  2. DOM节点的增加和减少
  3. 读写offset、client、scroll时
  4. 使用window.getComputedStyle的时候

对于第三点,现代浏览器都有渲染队列的机制,当某个元素触发浏览器发生重绘或者回流时,它就会进入一个渲染队列中,如果下面还有样式修改,那么同样入队,直到下面没有样式修改,浏览器会按照渲染队列批量执行回流,但是读取client等相关数据时,就会迫使浏览器清空渲染队列,发生回流,读取以下数据均会

offsetTop、offsetLeft、offsetWidth、offsetHeight
clientTop、clientLeft、clientWidth、clientHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
getComputedStyle()(IE中currentStyle)

重绘:

当改变元素时,只是改变了它的外观,比如背景颜色等,而没有影响到它的布局,这个时候会发生重绘,回流必定引起重绘,重绘不一定会引起回流 

减少及避免: 

  • 尽量避免频繁使用style,而是使用修改class的方式
  • 使用createDocumentFragment
  • 对于resize和scroll进行防抖节流处理 

隐藏元素 


盒模型 

标准盒模型 

  • 总宽度 = width + padding + margin + border
  • box-sizing:content-box 

怪异盒模型 

  • 总宽度 = width(包含了padding、border和内容宽度) + margin 
  • box-sizing:border-box 

HTML 

<head> 

head部分包括的元素:<base>, <link>, <meta>, <script>, <style>, <title>,其中<title>定义文档的标题,它是head部分唯一必需的元素 


<meta> 

<meta>元素可提供有关页面的元信息(meta-infomation),比如针对搜索引擎和更新频度的描述和关键词;<meta>标签位于文档的头部,不包含任何内容。<meta>标签的属性定义了文档相关联的名称/值对 

<meta>标签是HTML语言头部的一个辅助性标签,我们可以定义页面编码语言、搜索引擎优化、自动刷新并指向新的页面、控制页面缓冲、响应式视窗等 

charset属性: 

定义文档的字符编码,是HTML5新加的属性,一般放在第一个meta标签中,否则会引起乱码

content属性: 

定义与http-equiv或name属性相关的元信息 

http-equiv属性:

把content属性关联到HTTP头部,http-equiv类似于HTTP头部协议,它会给浏览器一些有用的信息,一帮助正确和精确地显示网页内容,与之对应的content属性,其内容就是各个参数的变量值

  • expires(期限):指网页在缓存中的过期时间,一旦网页过期,必须到服务器上重新传输
  • pragma(cache模式):禁止浏览器从本地计算机的缓存中访问页面内容
  • refresh(刷新):自动刷新并指向新页面
  • Set-Cookie(cookie设定):浏览器访问某个页面时将它存入缓存中,下次再次访问时就可以从缓存中读取,以提高速度。
  • Window-target(显示窗口的设定):强制页面在当前窗口以独立页面显示,可以用来防止别人在框架里调用你的页面
  • content-Type(显示字符集的设定):设定页面使用的字符集
  • imagtoolbar:指定是否显示图片工具栏
  • X-UA-Compatible:优先使用IE最新版本和Chrome

name属性: 

name属性主要用于描述页面,对应的属性时content,以便于搜索引擎机器人查找、分类(目前所有的搜索引擎都使用网上机器人自动查找meta值来给网页分类)

  • keywords(关键字):为搜索引擎提供关键字列表
  • description(简介):用来告诉搜索引擎你的网站主要内容
  • robots(机器人向导):用来告诉机器人哪些页面需要索引,哪些页面不需要
  • author(作者):标注网页的作者
  • copyright(版权):标注版权
  • generator:用来说明网站采用什么编辑器制作
  • revisit-after(重访):网站重访
  • viewport:能优化移动端浏览器的显示(屏幕的缩放)
  • format-detection:忽略电话号码和邮箱 

计算机网络

 cookie

 存储在用户本地终端上的数据

 翻译成人话:cookie是一些数据,存储在用户电脑的文本文件中

cookie的作用:

       当web服务器向浏览器发送web页面时,在连接关闭后,服务端不会记录用户的信息,所以cookie的作用就是用于解决“如何记录客户端的用户信息”:

  • 当用户访问web页面时,他的名字可以记录在cookie中
  • 当用户下一次访问该页面时,可以在cookie中读取用户访问记录

cookie是如何工作的:

        当我们访问某个网站时,服务器首先根据浏览器的编号生成一个cookie返回给客户端;客户端下次再访问的时候就会将自己本地的cookie加上url访问地址一同给服务器;服务器读出来以此来辨别用户的状态。

cookie的缺陷:

  •  数据大小:作为一个存储容器,cookie的大小限制在了4KB,只能存储一些简单信息
  • 安全性问题:在HTTP请求中cookie是明文传输(HTTPS不是)
  • 网络负担:cookie会被附加在每一个HTTP请求中,在HttpRequest和HttpResponse的header中都是要被传输的,增加了一些不必要的流量损失

session

 “会话控制”,session对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web页面之间跳转时,存储在session对象中的变量将不会丢失,而是在整个用户会话中一直存下去 

session的作用: 

      HTTP协议是无状态的,每次请求都是相互独立的,那么如果我们希望几个页面之间的请求是有关联的,比如,A页面是记录用户的信息,B页面是记录用户的订单记录,需要登录才能看到用户的信息,那我们不可能A页面登录后B页面再次登录,或者每次请求都带上用户密码。所以session出现了,它可以在一次会话中解决2次HTTP的请求关联,使客户端与服务器之间保持状态

session的机制: 

        当不同用户访问站点时,服务端会产生两个SessionID来区分该用户,而客户端会将相应的SessionID存放在cookie中


WebStorage 

 WebStorge的目的:

  • 提供一种在cookie之外存储会话数据的途径
  • 提供一种存储大量可以跨会话存在的数据机制

WebStorage分为两种:sessionStorage和localStorage,sessionStorage将数据保存在session中,浏览器关闭也就没了;而localStorage则一直将数据保存在客户端本地,除非主动删除数据。

localStorage:

  • 生命周期:关闭浏览器后数据依然保留,除非手动清除,否则一直在
  • 作用域:相同浏览器的不同标签在同源情况下可以共享localStorage

sessionStorage: 

  • 生命周期:关闭浏览器或者标签后即失效
  • 作用域:只在当前标签可用,当前标签的iframe中且同源可共享 

GET和POST 

  • GET把参数包含在URL上,POST通过request body传递参数
  • GET在浏览器回退时是无害的,而POST会再次提交请求
  • GET产生的URL地址可以被bookmark,而POST不行
  • GET请求会被浏览器主动cache,而POST不会,除非手动设置
  • GET请求只能进行URL编码,而POST支持多种编码方式
  • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留
  • GET请求在URL中传送的参数是有长度限制的,而POST没有
  • 对于参数的数据类型,GET只接受ASCII字符,而POST没有限制
  • GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息

GET和POST还有一个重大区别,GET产生一个TCP数据包,而POST产生两个,这是因为,对于GET请求,浏览器会把http head和data一并发送出去,服务器响应200;而对于POST来说,浏览器先发送header,服务器响应100,浏览器再发送data,服务器响应200 


缓存的优缺点 

优点: 

  • 减少了不必要的数据传输,节省宽带
  • 减少服务器的负担,提升网站性能
  • 加快了客户端加载网页的速度
  • 用户体验友好 

缺点: 

  • 资源如果由更改但是客户端不及时更新会造成用户获取信息滞后 

强缓存 

强缓存的强指的是强制,当浏览器去请求某个文件的时候,服务端就在response header里面对该文件做了缓存配置,缓存的时间、缓存类型都由服务端控制,具体表现为response header的cache-control

强缓存就是给资源设置个过期时间,客户端每次请求资源都会看是否过期,只有过期了才会去询问服务器

cache-control: 

1. max-age=xxx, public

        客户端和代理服务器都可以缓存该资源;客户端在xxx秒的有效期内,如果由请求该资源的需求就直接读取缓存,status code为200,如果客户端做了刷新操作,就向服务器发起HTTP请求

2. max-age=xxx, private

        只让客户端可以缓存该资源,代理服务器不缓存,客户端在xxx秒内直接读取缓存,status code为200

3. max-age=xxx, immutable

        客户端在xxx秒的有效期内,如果请求该资源的需求的话就直接读取缓存,status code为200,即使用户做了刷新操作,也不向服务器发送HTTP请求

4. no-cache

        跳过设置强缓存,但是不妨碍设置协商缓存;一般如果你做了强缓存,只有在强缓存失效了才走协商缓存,设置了no-cache就不会走强缓存,每次请求都会询问服务端

5. no-store

        不缓存,这个会让客户端、服务器都不缓存,也就没有所谓的强缓存和协商缓存了


协商缓存 

当用户给资源设置了过期时间,并且在过期后请求该资源时,这时会去请求服务器,这时候去请求服务器的过程就可以设置协商缓存,协商缓存时需要客户端和服务器两端进行交互的 

设置协商缓存: 

协商缓存的设置只需要在response header里面设置etag和last-modified即可

etag: 

每一个文件都有一个,文件发生改变就会变,就是个文件的hash,每一个文件唯一 

last-modified: 

文件的修改时间,精确到秒 

每次请求返回的response header中的etag和last-modified,在下次请求时request header就把这俩带上,服务器作比较,判断资源是否修改,如果资源不变,那就不变etag和last-modified,这时对客户端来说每次请求都是要进行协商缓存,请求过程:

发请求--> 看资源是否过期-->过期-->请求服务器-->服务器对比资源是否真的过期-->没过期-->返回304-->客户端用缓存的老资源

发请求-->看资源是否过期-->过期-->请求服务器-->服务器对比资源是否真的过期-->过期-->返回200-->客户端跟第一次接收资源一样,记下cache-control、etag和last-modified

补充一点: 

request header的if-none-matchedif-modified-since就是和response header的etag和last-modified相对应的

etag的作用: 

etag的出现后于last-modified,它的出现是为了解决last-modified的一些问题:

  1. 有些文件也许会周期性的更改,但是它的内容并没有改变,仅仅只是修改了更新时间
  2. 有些文件修改十分频繁,在1秒内修改多次,而if-modified-since的精度只有秒级
  3. 有些服务器不能精确的得到文件的最后修改时间 

如此看来,etag的准确度似乎比last-modified还要高,那为什么etag不完全替代last-modified呢?原因就是etag是hash值,有可能存在哈希碰撞, 因此etag和last-modified两个同时验证准确度最高


跨域

url的组成 

http://www.example.com:8080/path/file.html?key1=val1&key2=val2#SomewhereInTheDocument

url是由协议域名端口请求路径请求参数锚点组成的,其中,只有当协议、域名和端口完全一致才是同源

跨域的解决办法: 

JSONP 

JSONP的实现是基于script标签允许在别的源请求脚本,因为HTML5的type默认是text/javascript,因此获取数据会被当作js代码执行,所以后端需要在以json为格式的数据外,包裹一个函数,然后再外包一层传输用的JSON格式,函数的名字需要在前端设置好,因此需要在url的请求参数部分加上函数的名字,因为一般都是用js函数包裹json数据,因此称为JSON with Padding

【局限性】 

只能使用GET方法请求 

CORS 

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的 Web 应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器「不同的域、协议或端口」请求一个资源时,资源会发起一个「跨域 HTTP 请求」

 

反向代理 

前端不直接访问服务器,而是通过反向代理访问服务器,因为方向代理与服务器不受浏览器同源政策的影响,因此可以直接访问,只需要将浏览器与反向代理设置成同源即可


TCP 

三次握手 

三次握手,客户端先向服务端发起一个SYN包,进入SYN_SENT状态,服务端收到SYN后,给客户端返回一个ACK+SYN包,表示已收到SYN,并进入SYN_RECEIVE状态,最后客户端再向服务端发送一个ACK包表示确认,双方进入establish状态。

之所以是三次握手而不是两次,是因为如果只有两次,在服务端收到SYN后,向客户端返回一个ACK确认就进入establish状态,万一这个请求中间遇到网络情况而没有传给客户端,客户端一直是等待状态,后面服务端发送的信息客户端也接受不到了。

四次挥手 

之所以等待两个周期是因为最后客户端发送的ACK包可能会丢失,如果不等待2个周期的话,服务端在没收收到ACK包之前,会不停的重复发送FIN包而不关闭,所以得等待两个周期

TCP与UDP: 

  • 连接方面:TCP面向连接,UDP不需要连接 
  • 可靠性:TCP是可靠传输,一旦传出过程中丢包的话会进行重传;UDP是不可靠传输,但会最大努力交付
  • 工作效率:UDP实时性高,比TCP工作效率高
  • 支持多对多:TCP是点对点的,UDP支持一对一、一对多和多对多
  • 首部大小:TCP首部占20字节,UDP首部占8字节

TCP可靠传输: 

  • 校验和
  • 序列号和确认应答
  • 超时重传、滑动窗口和拥塞控制 

TCP的四元组: 

  • 四元组:源IP地址、目标IP地址、源端口、目标端口
  • 五元组:源IP地址、目标IP地址、源端口、目标端口、协议号
  • 七元组:源IP地址、目标IP地址、源端口、目标端口、协议号、服务类型、接口索引


HTTP和HTTPS 

区别: 

  1. HTTP是超文本传输协议,信息是明文传输,存在安全风险。HTTPS则解决HTTP不安全的缺陷,在TCP和HTTP网络层之间加入SSL/TLS安全协议,使得报文能够加密传输
  2. HTTP建立连接相对简单,TCP三次握手之后便可以进行HTTP的报文传输。而HTTPS在TCP三次握手之后,还需要进行SSL /TLS安全协议,才可进入加密报文传输
  3. HTTP的端口号是80,HTTPS的端口号是443
  4. HTTPS协议需要向CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的

HTTPS实现原理: 

  • 首先客户端向服务端发送一个随机值和一个客户端支持的加密算法,并连接到443端口。
  • 服务端收到以后,会返回另外一个随机值和一个协商好的加密算法,这个算法是刚才发送的那个算法的子集
  • 随后服务端会再次发送一个 CA 证书,这个 CA 证书实际上就是一个公钥,包含了一些信息(比如颁发机构和有效时间等)
  • 客户端收到以后会验证这个 CA 证书,比如验证是否过期,是否有效等等,如果验证未通过,会弹窗报错。
  • 如果验证成功,会生成一个随机值作为预主密钥,客户端使用刚才两个随机值和这个预主密钥组装成会话密钥;再使用刚才服务端发来的公钥进行加密发送给服务端;这个过程是一个非对称加密(公钥加密,私钥解密)
  • 服务端收到以后使用私钥解密,随后得到那两个随机值和预主密钥,随后再组装成会话密钥。
  • 客户端在向服务端发起一条信息,这条信息使用会话秘钥加密,用来验证服务端是否能收到加密的信息
  • 服务端收到以后使用刚才的会话密钥解密,在返回一个会话密钥加密的信息,双方收到以后 SSL 建立完成;这个过程是对称加密(加密和解密是同一个)

服务器渲染和客户端渲染 

互联网早期,用户使用浏览器浏览的都是一些没有复杂逻辑的、简单的页面,这些页面都是在后端将html拼接好的然后将之返回给前端完整的html文件,浏览器拿到这个html文件之后就可以直接解析展示了,而这也就是所谓的服务器端渲染了。而随着前端页面的复杂性提高,前端就不仅仅是普通的页面展示了,而可能添加了更多功能性的组件,复杂性更大,另外,彼时ajax的兴起,使得业界就开始推崇前后端分离的开发模式,即后端不提供完整的html页面,而是提供一些api使得前端可以获取到json数据,然后前端拿到json数据之后再在前端进行html页面的拼接,然后展示在浏览器上,这就是所谓的客户端渲染了,这样前端就可以专注UI的开发,后端专注于逻辑的开发。 

服务器渲染(SSR Server Site Rendering): 

  • 有利于SEO,首屏加载快,但是重复请求次数多,开发效率低,服务器压力大
  • 渲染的时候返回的是完整的html格式 

客户端渲染(CSR Client Site Rendering): 

  • 不利于SEO,首屏加载慢,前后端分离开发,交互速度快,体验好
  • 渲染的时候返回的是json数据格式,由浏览器完成渲染 

https://www.cnblogs.com/zhuzhenwei918/p/8795945.html


JWT(JSON Web Token) 

在JWT出现之前,验证客户端的方式就是通过token,方式如下:

  • 用户输入账号密码后,向服务端发起请求,服务端生成token返回给客户端,然后下次客户端请求数据的时候会携带token,服务器收到之后,会与之前保存的token进行验证,验证通过后返回数据 
  • 如果有大量用户请求数据,服务端要保存很多token,压力巨大

JWT相当于把数据转化为了JSON对象,分为三个部分:头部、负载和签名,之间用点(.)连接形成一个字符串

头部(header):

保存JWT的元数据,表明自己所使用的hash算法,以JSON对象形式存储,转化为Base64URL格式 

{
  "alg": "HS256",
  "typ": "JWT"
}

负载(payload):

用来存放自己的数据,包含需要传递的数据,JWT指定默认七个字段供选择 

iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT

签名(signature):

由三部分组成:header(base64后)、payload(base64)和secret ,header和payload之间用点(.)连接组成字符串,然后通过header声明的加密方式和算法的密钥(secret)加密,形成一个签名。secret是保存在服务器中的,是服务端的私钥,在任何场景都不应该流露出去


CDN缓存

如果接入CDN的话,DNS解析权也是会改变的,会把DNS解析权交给CNAME指向的CDN专用DNS服务器

其次就是缓存问题,正常情况下,浏览器在发起请求之前,会先查看本地缓存,如果过期的话再去向服务端发起请求

如果使用了CDN,浏览器会先查看本地缓存,如果未命中,会向CDN边缘节点发起请求,如果没有过期直接返回数据,如果过期,则CDN还需要向源站发起回源请求,来拉取最新数据 


从浏览器输入url后都经历了什么 

  1. 浏览器查找是否存在缓存,并比较缓存是否过期
  2. DNS解析URL对象的IP
  3. 根据IP建立TCP连接
  4. HTTP发起请求
  5. 服务器处理请求,浏览器接收HTTP响应
  6. 渲染页面,构建DOM树
  7. 关闭TCP连接


算法 

 Fisher-Yates shuffle

// 洗牌算法
// 打乱数组内元素的顺序
const arr = [1, 2, 3, 4, 5]
let length = arr.length, index
for (let i=length-1; i>=0; i--) {
  index = Math.floor(Math.random() * i)
  [arr[i], arr[index]] = [arr[index], arr[i]]
}

Vue

 vue的基本概念 

一个Vue需要运行起来,需要模板通过编译生成AST,再由AST生成Vue的render函数(渲染函数),渲染函数结合数据生成Virtual DOM树(虚拟DOM),Diff和Patch后生成新的UI

  • 模板:Vue的模板基于纯HTML,基于Vue的模板语法,我们可以比较方便地声明数据和UI的关系 
  • AST:AST是Abstract Syntax Tree(抽象语法树)的简称,Vue使用HTML的Parser(解析器)将HTML模板解析为AST,并且对AST进行一些优化的标记处理,提取最大的静态树,方便Virtual DOM时直接跳过Diff
  • Virtual DOM:虚拟DOM树,Vue的Virtual DOM Patching算法是基于Suabbdom的实现,并在些基础上作出了很多的调整和改进
  • Watcher:每个Vue组件都有一个对应的watcher,这个watcher将会在组件render的时候收集所依赖的数据,并在依赖有更新的时候,触发组件重新渲染

上图中,render函数可以作为一道分割线,render函数的左边可以称为编译期,将Vue的模板转换为渲染函数,render函数的右边是Vue的运行期,主要是基于渲染函数生成的Virtual DOM树,Diff和Patch

Vue的渲染流程: 

  1. new Vue,执行初始化
  2.  挂载$mount方法,通过自定义render方法templateel等生成render函数
  3. 通过watcher监听数据的变化
  4. 当数据发生变化的时候,render函数执行生成VNode对象
  5. 通过patch方法,对比新旧VNode对象,通过DOM Diff算法,添加、修改、删除真正的DOM元素

reder函数 

每一个元素都是一个DOM节点,每段文字甚至注释也是,高效更新这些节点十分困难,不过我们只需要告诉Vue我们希望页面上的HTML是什么,这可以是在一个模板里: 

<h1>{{title}}</h1>

或者在一个渲染函数里:

reder: function (createElement) {
    return createElement('h1', this.title)
}

当我们的数据title发生变化的时候,vue会自动更新页面 

createElement返回的不是一个实际的DOM元素,而是一个虚拟节点Virtual Node,简称VNode虚拟DOM就是我们对由Vue组件树建立起来的整个VNode树的称呼 

createElement: 

        在Vue中,h被视为createElement的别名,const h = this.$createElement,因此render函数也可以写作:

reder: h => {
    return h(...)
}

        下列代码来自Vue官网,展现了createElement函数的参数:

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一个 HTML 标签名、组件选项对象,或者
  // resolve 了上述任何一种的一个 async 函数。必填项。
  'div',

  // {Object}
  // 一个与模板中 attribute 对应的数据对象。可选。
  {
    // 与 `v-bind:class` 的 API 相同,
    // 接受一个字符串、对象或字符串和对象组成的数组
    'class': {
      foo: true,
      bar: false
    },
    // 与 `v-bind:style` 的 API 相同,
    // 接受一个字符串、对象,或对象组成的数组
    style: {
      color: 'red',
      fontSize: '14px'
    },
    // 普通的 HTML attribute
    attrs: {
      id: 'foo'
    },
    // 组件 prop
    props: {
      myProp: 'bar'
    },
    // DOM property
    domProps: {
      innerHTML: 'baz'
    },
    // 事件监听器在 `on` 内,
    // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
    // 需要在处理函数中手动检查 keyCode。
    on: {
      click: this.clickHandler
    },
    // 仅用于组件,用于监听原生事件,而不是组件内部使用
    // `vm.$emit` 触发的事件。
    nativeOn: {
      click: this.nativeClickHandler
    },
    // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
    // 赋值,因为 Vue 已经自动为你进行了同步。
    directives: [
      {
        name: 'my-custom-directive',
        value: '2',
        expression: '1 + 1',
        arg: 'foo',
        modifiers: {
          bar: true
        }
      }
    ],
    // 作用域插槽的格式为
    // { name: props => VNode | Array<VNode> }
    scopedSlots: {
      default: props => createElement('span', props.text)
    },
    // 如果组件是其它组件的子组件,需为插槽指定名称
    slot: 'name-of-slot',
    // 其它特殊顶层 property
    key: 'myKey',
    ref: 'myRef',
    // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
    // 那么 `$refs.myRef` 会变成一个数组。
    refInFor: true
  },

  // {String | Array}
  // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
  // 也可以使用字符串来生成“文本虚拟节点”。可选。
  [
    '先写一些文字',
    createElement('h1', '一则头条'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

虚拟DOM、Patch、Diff算法 

虚拟DOM: 

        使用原生的js去操作DOM是十分麻烦的一件事,并且一遍又一遍的渲染DOM是非常消耗性能的事,因此出现了虚拟DOM

        虚拟DOM实际上是JavaScript对象,是对真实DOM的抽象,该对象包含了真实DOM中的结构及其属性,用于对比和真实DOM的差异,从而进行局部渲染来达到优化性能的目的

真实元素节点: 

<div id="app">
  <p class="title">Hello world!</p>
</div>

虚拟DOM节点:

VNode = {
  tag: 'div',
  attrs: {
    id: 'app'
  },
  children: [
    {
      tag: 'p',
      text: 'Hello world!',
      attrs: {
        class: 'title'
      }
    }
  ]
}

Patch和Diff算法:

一开始Vue回根据真实DOM生成虚拟DOM,当虚拟DOM某个节点的数据改变后会生成一个新的VNode,然后VNode和oldVNode对比,把不同的地方修改在真实DOM上,最后再使得oldVNode的值为VNode

Diff的过程就是调用patch函数,比较新老节点,一边比较一边打补丁(patch) 

Patch源码

//patch函数  oldVnode:老节点   vnode:新节点
function patch (oldVnode, vnode) {
    ...
    if (sameVnode(oldVnode, vnode)) {
        patchVnode(oldVnode, vnode)    //如果新老节点是同一节点,那么进一步通过patchVnode来比较子节点
    } else {
        /* -----否则新节点直接替换老节点----- */
        const oEl = oldVnode.el // 当前oldVnode对应的真实元素节点
        let parentEle = api.parentNode(oEl)  // 父元素
        createEle(vnode)  // 根据Vnode生成新元素
        if (parentEle !== null) {
            api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 将新元素添加进父元素
            api.removeChild(parentEle, oldVnode.el)  // 移除以前的旧元素节点
            oldVnode = null
        }
    }
    ...
    return vnode
}

//判断两节点是否为同一节点
function sameVnode (a, b) {
    return (
	    a.key === b.key &&  // key值
	    a.tag === b.tag &&  // 标签名
	    a.isComment === b.isComment &&  // 是否为注释节点
	    // 是否都定义了data,data包含一些具体信息,例如onclick , style
	    isDef(a.data) === isDef(b.data) &&  
	    sameInputType(a, b) // 当标签是<input>的时候,type必须相同
    )
}

可以看出,patch函数大致分为两种情况:

  1. 如果是同一节点,执行patchVnode进行子节点比较
  2. 如果不是,新节点直接替换老节点

因为Diff是同层比较,不存在跨级比较,因此如果不是同一节点,子节点一样也视为情况二 

patchVnode: 

function patchVnode (oldVnode, vnode) {
    const el = vnode.el = oldVnode.el           //找到对应的真实DOM
    let i, oldCh = oldVnode.children, ch = vnode.children     
    if (oldVnode === vnode) return         //如果新老节点相同,直接返回
    if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
        //如果新老节点都有文本节点且不相等,那么新节点的文本节点替换老节点的文本节点
        api.setTextContent(el, vnode.text)  
    }else {
        updateEle(el, vnode, oldVnode)
        if (oldCh && ch && oldCh !== ch) {
            //如果新老节点都有子节点,执行updateChildren比较子节点[很重要也很复杂,下面展开介绍]
            updateChildren(el, oldCh, ch)
        }else if (ch){
            //如果新节点有子节点而老节点没有子节点,那么将新节点的子节点添加到老节点上
            createEle(vnode)
        }else if (oldCh){
            //如果新节点没有子节点而老节点有子节点,那么删除老节点的子节点
            api.removeChildren(el)
        }
    }
}

 从上述代码可以看出,当两个节点为同一节点时:

  • 新老节点一样,直接返回
  • 老节点有子节点,新节点没有:删除老节点的子节点
  • 老节点没有子节点,新节点有:新节点的子节点直接append到老节点
  • 都只有文本节点:直接用新节点的文本节点替换老的文本节点
  • 都有子节点:updateChildren

updateChildren: 

  updateChildren (parentElm, oldCh, newCh) {
    let oldStartIdx = 0, newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx
    let idxInOld
    let elmToMove
    let before
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
        if (oldStartVnode == null) {   // 对于vnode.key的比较,会把oldVnode = null
            oldStartVnode = oldCh[++oldStartIdx] 
        }else if (oldEndVnode == null) {
            oldEndVnode = oldCh[--oldEndIdx]
        }else if (newStartVnode == null) {
            newStartVnode = newCh[++newStartIdx]
        }else if (newEndVnode == null) {
            newEndVnode = newCh[--newEndIdx]
        }else if (sameVnode(oldStartVnode, newStartVnode)) {
            patchVnode(oldStartVnode, newStartVnode)
            oldStartVnode = oldCh[++oldStartIdx]
            newStartVnode = newCh[++newStartIdx]
        }else if (sameVnode(oldEndVnode, newEndVnode)) {
            patchVnode(oldEndVnode, newEndVnode)
            oldEndVnode = oldCh[--oldEndIdx]
            newEndVnode = newCh[--newEndIdx]
        }else if (sameVnode(oldStartVnode, newEndVnode)) {
            patchVnode(oldStartVnode, newEndVnode)
            api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el))
            oldStartVnode = oldCh[++oldStartIdx]
            newEndVnode = newCh[--newEndIdx]
        }else if (sameVnode(oldEndVnode, newStartVnode)) {
            patchVnode(oldEndVnode, newStartVnode)
            api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el)
            oldEndVnode = oldCh[--oldEndIdx]
            newStartVnode = newCh[++newStartIdx]
        }else {
           // 使用key时的比较
            if (oldKeyToIdx === undefined) {
                oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 有key生成index表
            }
            idxInOld = oldKeyToIdx[newStartVnode.key]
            if (!idxInOld) {
                api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
                newStartVnode = newCh[++newStartIdx]
            }
            else {
                elmToMove = oldCh[idxInOld]
                if (elmToMove.sel !== newStartVnode.sel) {
                    api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)
                }else {
                    patchVnode(elmToMove, newStartVnode)
                    oldCh[idxInOld] = null
                    api.insertBefore(parentElm, elmToMove.el, oldStartVnode.el)
                }
                newStartVnode = newCh[++newStartIdx]
            }
        }
    }
    if (oldStartIdx > oldEndIdx) {
        before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].el
        addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)
    }else if (newStartIdx > newEndIdx) {
        removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
}

在新老两个VNode节点的头尾都有一个指针 

在遍历的时候,oldStartVnode、oldEndVnode与newStartVnode、newEndVnode会先进行两两比较,一共有四种比较方式,当其中两个能匹配上那么真实DOM中相应节点会移到VNode相应的位置:

  •  oldStartVnode和newStartVnode匹配,位置不动,oldStartIdx,newStartIdx指针后移
  •  oldEndVnode和newEndVnode匹配,位置不动,oldEndIdx,newEndIdx指针前移
  •  oldStartVnode和newEndVnode匹配,oldStartVnode移动到newEndVnode所在的位置,   oldStartIdx指针前移,newEndIdx指针后移
  •  oldEndVnode和newStartIdx匹配,oldEndVnode移动到newStartVnode所在位置,   oldEndIdx指针后移,newStartIdx指针前移

此时已完成了新旧节点首位子节点的匹配,倘若以上4种方式都没能匹配上,如果设置了key,就会用key进行比较,遍历剩下的节点,如果在newVNode中找到一致key的旧的VNode节点,并且同时满足sameVnode,patchVnode,那么这个节点将得到复用。 

key的作用为:

  1. 决定节点是否可以复用
  2. 建立key-index的索引,主要是替代遍历,提升性能 

最后通过指针位置来判断oldCh和newCh哪一个先遍历完,oldStartIdx > oldEndIdx表示oldCh先遍历完,那么将多余的vCh根据index添加到DOM中;newStartIdx > newEndIdx表示newCh先遍历完,那么就在DOM中删除多余的节点 

 参考:掘金

            虚拟dom和diff算法 - 简书


双向数据绑定 

Vue是一个MVVM框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化。在Vue中,如果使用vuex,实际上数据还是单向的,之所以说数据双向绑定,这是相对于UI控件来说的,对于我们处理表单,vue的双向数据绑定用起来会十分舒服 

Vue是采用数据劫持结合发布者-订阅者模式的方式,通过Object.definePropoty()来劫持各个属性的getter,setter,在数据变动时发布消息给订阅者,触发相应的监听回调 

实现双向数据绑定主要包含两个方面,数据变化更新视图,视图变化更新数据。view更新data可以通过事件的监听;data更新view,则需要通过Object.defineProperty()对属性设置一个set函数,当数据改变了就会触发这个函数,所以只需要将一些更新的方法放在里面就可以实现data更新view了。

MVVM实现过程:

设置一个数据监听器Observer,用来监听所有属性。如果属性发生了变化,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个的,所以我们需要一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化为一个订阅者Watcher,并替换模板数据或者绑定的相应函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此实现数据的双向绑定需要三个步骤:

  1. 实现一个监听器Observer,用来劫持并监听所有属性,如果有变动,就通知订阅者
  2. 实现一个订阅者Watcher,用来收到属性的变化通知并执行相应的函数,从而更新视图
  3. 实现一个解析器Compile,用来扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器

preview


Object.defineProperty 

通过Object.defineProperty()为对象定义属性有两种形式:

  • 数据描述符(特有属性:value,writable)
  • 存取描述符(是由一对getter、setter函数功能来描述的属性) 

生命周期 

beforeCreate: 

创建之前,此时还没有data和method

created:

实例已经创建完成之后被调用,实例已完成以下配置:数据观测、属性和方法的以运算,watch/event事件回调,完成了data数据的初始化,el没有。在这个生命周期中可以调用methods中方法,改变data中的数据

beforeMounte: 

在渲染之前,实例完成以下配置:编译模板,把data里面的数据和模板生成html,完成了el和data初始化,此时还没有挂载html到页面上

mounted: 

页面已经渲染完成,并且vm实例中已经添加完$el了,已经替换掉那些DOM元素(双括号中的变量),这个时候可以操作DOM,但是要获取元素高度等属性需要使用nextTick(),mounted只执行一次

beforeUpdate:

data改变之后,对应的组件重新渲染之前

updated: 

data改变之后,对应的组件重新渲染完成 

beforeDestroy: 

在实例销毁之前,此时实例仍可以使用

destroyed: 

实例销毁后 


vue-router 

传统的页面应用,是用一些超链接来实现页面切换和跳转的,在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换,路由模块的本质,就是建立起url和页面之间的映射关系 

至于我们为啥不能用a标签,这是因为用Vue做的都是单页应用(当你的项目准备打包时,运行npm run build时,就会生成dist文件夹,这里面只有静态资源和一个index.html页面),所以你写的<a></a>标签是不起作用的,你必须使用vue-router来进行管理。

原理:

SPA:单一页面应用程序,只有一个完整的页面;它在加载页面时,不会加载整个页面,而是只更新某个指定的容器中内容。SPA的核心之一是:更新视图而不重新请求页面

hash模式: 

vue-router默认hash模式,使用url的hash来模拟一个完整的url,于是当url改变的时候,页面不会重新加载。hash(#)是url的锚点,代表网页中的一个位置,单单改变#后的部分,浏览器只会滚到相应位置,不会重新加载网页,同时会在浏览器的访问历史里增加一个记录。原理是onchange事件(检测hash值变化),可以在window对象监听这个事件

history模式:

由于hash模式会在url中自带#,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: 'history'",这种模式充分利用了html5 history interface 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器记录栈,在当前已有的 back、forward、go 基础之上,它们提供了对历史记录修改的功能。只是当它们执行修改时,虽然改变了当前的 URL ,但浏览器不会立即向后端发送请求。

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。
所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

$route:

$route是路由信息对象,包括path、params、hash、query、fullPath、matched、name等路由信息参数 

$router: 

$router是路由实例对象,即使用new VueRouter创建的实例,包括路由跳转方法,钩子函数等

$router.push和$router.replace的区别:push方法会向history栈中添加新纪录,而replace是替换当前的history记录 

导航钩子: 

vue-router的导航钩子,主要作用是拦截导航,让他完成跳转或取消 

【全局导航钩子】 

  • 前置守卫:beforeEach((to, from, next)=>{}) 
  • 解析守卫:beforeResolve
  • 后置钩子:afterEach()

【路由独享守卫】 

  • beforeEnter:只在进入路由时触发 

【组件内的守卫】 

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave 
const UserDetails = {
  template: `...`,
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
}

完整的导航解析流程:

  1. 导航被激活
  2. 在失活的组件里调用beforeRouteLeave守卫
  3. 调用全局的beforeEach守卫
  4. 在重用的组件里调用beforeRouteUpdate守卫
  5. 在路由配置里调用beforeEnter
  6. 解析异步路由组件
  7. 在被激活的组件里调用beforeRouteEnter守卫
  8. 调用全局的beforeResolve守卫
  9. 导航被确认
  10. 调用全局的afterEach守卫
  11. 触发Dom更新
  12. 调用beforeRouteEnter守卫中传给next的回调函数,创建好的组件实例会作为回调函数的参数传入

Vuex 

Vuex是实现组件全局状态(数据)管理的以中机制,可以方便的实现组件之间数据的共享 

优点: 

  • 能够在Vuex中集中管理共享的数据,易于开发和后期维护
  • 能够高效地实现组件之间的数据共享,提高开发效率
  • 存储在Vuex中的数据都是响应式的,能够实现保持数据与页面的同步 

如果是小型单页面开发,就不太推荐使用Vuex 

import Vuex from 'vuex'
Vue.use(Vuex)

const store = new Viex.Store({
    
})

state:

所有共享的数据都要同意放在Store中的state中进行存储 

访问方式有两种:

  1. this.$store.state.全局数据名称
  2. 从vuex中按需引入mapState函数,将当前组件需要的全局数据,映射为当前组件的computed计算属性
    import { mapState } from 'vuex'
    
    
    computed : {
        ...mapState(['数据名称'])
    }

mutation:

Mutation用于更改Store中的数据

  • 只能通过mutation变更store数据,不可以直接操作store中的数据
  • 通过这种方式,可以集中监控所有数据变化。直接操作store数据是无法进行监控的 
mutations: {
  // 自增
  add(state) {
    state.count++
  },
  // 增加一定量
  addNum(state, payload) {
    state.count += payload.number
  }
}

在mutations中定义的函数会有一个默认参数state,这个就是存储在Store中的state对象,有时需要携带一些额外的参数,此处参数被称为mutation的载荷payload,如果仅有一个参数时,那payload就是这个参数值,如果时多个参数,那就会以对象的形式传递

Vuex的store中的State是响应式的,当State中的数据发生改变时,Vue组件也会自动更新,所以我们需要提前在store中初始化好所需的属性,当给state中的对象添加新的属性时,使用如下方法:

  • Vue.set(obj, 'newProp', 'propValue')
  • 用新对象给旧对象重新赋值
updateUserInfo(state) {
  // 方式一
  Vue.set('user', 'address', 'shenzhen')
  // 方式二
  state.user = {
    ...state.user,
    'address': 'shenzhen'
  }
}

调用mutation有两种方法:

// 方法一
this.$store.commit(方法名)
// 方法二
import { mapMutations } from 'vuex'
// ...
methods: {
  ...mapMutations('add', 'addN'),
  // 当前组件设置的click方法
  addCount() {
    this.add()
  }
}

action:

Action类似于Mutation,但是是用于处理异步任务的,比如网络请求等,如果通过异步操作变更数据,必须通过Action,而不能使用Mutation,但在Action中还是通过触发Mutation的方式间接变更数据

在actions中定义方法,都会有默认值context

  • context是和store对象具有相同方法和属性的对象,但不是同一个
  • 可以通过context进行commit相关操作,可以获取context.state数据
export default new Vuex.Store({
  state: {
    count: 0
  },
 //只有 mutations 中定义的函数,才有权力修改 state 中的数据
  mutations: {
    // 自增
    add(state) {
      state.count++
    }
  },
  actions: {
    addAsync(context) {
      setTimeout(() => {
      //在 action 中,不能直接修改 state 中的数据
      //必须通过 context.commit() 触发某个 mutation 才行
        context.commit('add')
      }, 1000);
    }
  }
})

调用方法:

  • this.$store.dispatch(方法名)
  • 导入mapActions函数

getter: 

Getters用于对Store中的数据进行加工处理成新的数据,类似于Vue中的计算属性

Store中数据发生变化,Getters的数据也会跟随变化

//定义 Getter
const store = new Vuex.Store({
    state:{
    count: 0
    },
    getters:{
        showNum(state){
          return '当前Count值为:['+state.count']'
        }
      }
})

 调用方法:

  • this.$store.getters.名称
  • 导入mapGetters 函数,将需要的getters函数映射为当前组件的computed方法

modules: 

Module是模块的意思,为什么会在Vuex中使用模块呢?

  • Vues使用单一状态树,意味着很多状态都会交给Vuex来管理

  • 当应用变的非常复杂时,Store对象就可能变的相当臃肿

  • 为解决这个问题,Vuex允许我们将store分割成模块(Module),并且每个模块拥有自己的State、Mutation、Actions、Getters


浏览器

渲染引擎工作流程 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值