整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Vue + Koa從零打造一個H5頁面可視化編輯器-Quark-h5

          想必你一定使用過易企秀或百度H5等微場景生成工具制作過炫酷的h5頁面,除了感嘆其神奇之處有沒有想過其實現方式呢?本文從零開始實現一個H5編輯器項目完整設計思路和主要實現步驟,并開源前后端代碼。有需要的小伙伴可以按照該教程從零實現自己的H5編輯器。(實現起來并不復雜,該教程只是提供思路,并非最佳實踐)

          Github: https://github.com/huangwei9527/quark-h5

          演示地址:http://47.104.247.183:4000/

          演示帳號密碼均admin

          編輯器預覽:



          技術棧

          前端: vue: 模塊化開發(fā)少不了angular,react,vue三選一,這里選擇了vue。 vuex: 狀態(tài)管理 sass: css預編譯器。 element-ui:不造輪子,有現成的優(yōu)秀的vue組件庫當然要用起來。沒有的自己再封裝一些就可以了。 loadsh:工具類

          服務端: koa:后端語言采用nodejs,koa文檔和學習資料也比較多,express原班人馬打造,這個正合適。 mongodb:一個基于分布式文件存儲的數據庫,比較靈活。

          閱讀前準備

          1、了解vue技術棧開發(fā) 2、了解koa 3、了解mongodb

          工程搭建

          基于vue-cli3環(huán)境搭建

          • 如何規(guī)劃好我們項目的目錄結構?首先我們需要有一個目錄作為前端項目,一個目錄作為后端項目。所以我們要對vue-cli 生成的項目結構做一下改造:
          ···
          ·
          |-- client				// 原 src 目錄,改成 client 用作前端項目目錄
          |-- server				// 新增 server 用于服務端項目目錄
          |-- engine-template		// 新增 engine-template 用于頁面模板庫目錄
          |-- docs				// 新增 docs 預留編寫項目文檔目錄
          ·
          ···
          復制代碼
          • 這樣的話 我們需要再把我們webpack配置文件稍作一下調整,首先是把原先的編譯指向src的目錄改成client,其次為了 npm run build 能正常編譯 client 我們也需要為 babel-loader 再增加一個編譯目錄: 根目錄新增vue.config.js,目的是為了改造項目入口,改為:client/main.js module.exports = { pages: { index: { entry: "client/main.js" } } } 復制代碼 babel-loader能正常編譯 client, engine-template目錄, 在vue.config.js新增如下配置 // 擴展 webpack 配置 chainWebpack: config => { config.module .rule('js') .include.add(/engine-template/).end() .include.add(/client/).end() .use('babel') .loader('babel-loader') .tap(options => { // 修改它的選項... return options }) } 復制代碼

          這樣我們搭建起來一個簡易的項目目錄結構。

          工程目錄結構

          |-- client					--------前端項目界面代碼
              |--common					--------前端界面對應靜態(tài)資源
              |--components				--------組件
              |--config					--------配置文件
              |--eventBus					--------eventBus
              |--filter					--------過濾器
              |--mixins					--------混入
              |--pages					--------頁面
              |--router					--------路由配置
              |--store					--------vuex狀態(tài)管理
              |--service					--------axios封裝
              |--App.vue					--------App
              |--main.js					--------入口文件
              |--permission.js			--------權限控制
          |-- server					--------服務器端項目代碼
              |--confog					--------數據庫鏈接相關
              |--middleware				--------中間件
              |--models					--------Schema和Model
              |--routes					--------路由
              |--views					--------ejs頁面模板
              |--public					--------靜態(tài)資源
              |--utils					--------工具方法
              |--app.js					--------服務端入口
          |-- common					--------前后端公用代碼模塊(如加解密)
          |-- engine-template			--------頁面模板引擎,使用webpack打包成js提供頁面引用
          |-- docs					--------預留編寫項目文檔目錄
          |-- config.json				--------配置文件
          復制代碼

          前端編輯器實現

          編輯器的實現思路是:編輯器生成頁面JSON數據,服務端負責存取JSON數據,渲染時從服務端取數據JSON交給前端模板處理。



          數據結構

          確認了實現邏輯,數據結構也是非常重要的,把一個頁面定義成一個JSON數據,數據結構大致是這樣的:

          頁面工程數據接口

          {
          	title: '', // 標題
          	description: '', //描述
          	coverImage: '', // 封面
          	auther: '', // 作者
          	script: '', // 頁面插入腳本
          	width: 375, // 高
          	height: 644, // 寬
          	pages: [], // 多頁頁面
          	shareConfig: {}, // 微信分享配置
          	pageMode: 0, // 渲染模式,用于擴展多種模式渲染,翻頁h5/長頁/PC頁面等等
          }
          復制代碼

          多頁頁面pages其中一頁數據結構:

          {
          	name: '',
          	elements: [], // 頁面元素
          	commonStyle: {
          		backgroundColor: '',
          		backgroundImage: '',
          		backgroundSize: 'cover'
          	},
          	config: {}
          }
          復制代碼

          元素數據結構:

          {
          	elName: '', // 組件名
          	animations: [], // 圖層的動畫,可以支持多個動畫
          	commonStyle: {}, // 公共樣式,默認樣式
          	events: [], // 事件配置數據,每個圖層可以添加多個事件
          	propsValue: {}, // 屬性參數
          	value: '', // 綁定值
          	valueType: 'String', // 值類型
          	isForm: false // 是否是表單控件,用于表單提交時獲取表單數據
          }
          復制代碼

          編輯器整體設計

          • 一個組件選擇區(qū),提供使用者選擇需要的組件
          • 一個編輯預覽畫板,提供使用者拖拽排序頁面預覽的功能
          • 一個組件屬性編輯,提供給使用者編輯組件內部props、公共樣式和動畫的功能 如圖:


          用戶在左側組件區(qū)域選擇組件添加到頁面上,編輯區(qū)域通過動態(tài)組件特性渲染出每個元素組件。


          最后,點擊保存將頁面數據提交到數據庫。至于數據怎么轉成靜態(tài) HTML方法有很多。還有頁面數據我們全部都有,我們可以做頁面的預渲染,骨架屏,ssr,編譯時優(yōu)化等等。而且我們也可以對產出的活動頁做數據分析~有很多想象的空間。

          核心代碼

          編輯器核心代碼,基于 Vue 動態(tài)組件特性實現:



          為大家附上 Vue 官方文檔:cn.vuejs.org/v2/api/#is

          畫板元素渲染

          編輯畫板只需要循環(huán)遍歷pages[i].elements數組,將里面的元素組件JSON數據取出,通過動態(tài)組件渲染出各個組件,支持拖拽改變位置尺寸.

          元素組件管理

          在client目錄新建plugins來管理組件庫。也可以將該組件庫發(fā)到npm上工程中通過npm管理

          組件庫

          編寫組件,考慮的是組件庫,所以我們竟可能讓我們的組件支持全局引入和按需引入,如果全局引入,那么所有的組件需要要注冊到Vue component 上,并導出:

          client/plugins下新建index.js入口文件

          ```
          /**
           * 組件庫入口
           * */
          import Text from './text'
          // 所有組件列表
          const components = [
          	Text
          ]
          // 定義 install 方法,接收 Vue 作為參數
          const install = function (Vue) {
          	// 判斷是否安裝,安裝過就不繼續(xù)往下執(zhí)行
          	if (install.installed) return
          	install.installed = true
          	// 遍歷注冊所有組件
          	components.map(component => Vue.component(component.name, component))
          }
          
          // 檢測到 Vue 才執(zhí)行,畢竟我們是基于 Vue 的
          if (typeof window !== 'undefined' && window.Vue) {
          	install(window.Vue)
          }
          
          export default {
          	install,
          	// 所有組件,必須具有 install,才能使用 Vue.use()
          	Text
          }
          ```
          復制代碼

          組件開發(fā)

          示例: text文本組件

          client/plugins下新建text組件目錄

          |-- text                --------text組件
              |--src              --------資源
              	|--index.vue    --------組件
              |--index.js         --------入口
          復制代碼

          text/index.js

          // 為組件提供 install 方法,供組件對外按需引入
          import Component from './src/index'
          Component.install = Vue => {
          	Vue.component(Component.name, Component)
          }
          export default Component
          復制代碼

          text/src/index.vue

          <!--text.vue-->
          <template>
            <div class="qk-text">
              {{text}}
            </div>
          </template>
          
          <script>
          	export default {
          		name: 'QkText', // 這個名字很重要,它就是未來的標簽名<qk-text></qk-text>
          		props: {
          			text: {
          				type: String,
          				default: '這是一段文字'
                		}
          		}
          	}
          </script>
          
          <style lang="scss" scoped>
          </style>
          復制代碼

          編輯器里使用組件庫:

          // 引入組件庫
          import QKUI from 'client/plugins/index'
          // 注冊組件庫
          Vue.use(QKUI)
          
          // 使用:
          <qk-text text="這是一段文字"></qk-text>
          復制代碼

          按照這個組件開發(fā)方式我們可以擴展任意多的組件,來豐富組件庫

          需要注意的是這里的組件最外層寬高都要求是100%

          配置文件

          Quark-h5編輯器左側選擇組件區(qū)域可以通過一個配置文件定義可選組件 新建一個ele-config.js配置文件:

          export default [
          	{
          		title: '基礎組件',
          		components: [
          			{
          				elName: 'qk-text', // 組件名,與組件庫名稱一致
          				title: '文字',
          				icon: 'iconfont iconwenben',
          				// 給每個組件配置默認顯示樣式
          				defaultStyle: {
          					height: 40
          				}
          			}
          		]
          	},
          	{
          		title: '表單組件',
          		components: []
          	},
          	{
          		title: '功能組件',
          		components: []
          	},
          	{
          		title: '業(yè)務組件',
          		components: []
          	}
          ]
          復制代碼

          公共方法中提供一個function 通過組件名和默認樣式獲取元素組件JSON,getElementConfigJson(elName, defaultStyle)方法

          元素屬性編輯

          公共屬性樣式編輯

          公共樣式屬性編輯比較簡單就是對元素JSON對象commonStyles字段進行編輯操作

          props屬性編輯

          1.為組件的每一個prop屬性開發(fā)一個屬性編輯組件. 例如:QkText組件需要text屬性,新增一個attr-qk-text組件來操作該屬性 2.獲取組件prop對象 3.遍歷prop對象key, 通過key判斷顯示哪些屬性編輯組件

          元素添加動畫實現

          動畫效果引入Animate.css動畫庫。元素組件動畫,可以支持多個動畫。數據存在元素JSON對象animations數組里。

          選擇面板hover預覽動畫


          監(jiān)聽mouseover和mouseleave,當鼠標移入時將動畫className添加入到元素上,鼠標移出時去掉動畫lassName。這樣就實現了hover預覽動畫


          編輯預覽動畫

          組件編輯時支持動畫預覽和單個動畫預覽。

          封裝一個動畫執(zhí)行方法


          /**
           * 動畫方法, 將動畫css加入到元素上,返回promise提供執(zhí)行后續(xù)操作(將動畫重置)
           * @param $el 當前被執(zhí)行動畫的元素
           * @param animationList 動畫列表
           * @param isDebugger 動畫列表
           * @returns {Promise<void>}
           */
          export default async function runAnimation($el, animationList = [], isDebug , callback){
          	let playFn = function (animation) {
          		return new Promise(resolve => {
          			$el.style.animationName =  animation.type
          			$el.style.animationDuration =  `${animation.duration}s`
          			// 如果是循環(huán)播放就將循環(huán)次數置為1,這樣有效避免編輯時因為預覽循環(huán)播放組件播放動畫無法觸發(fā)animationend來暫停組件動畫
          			$el.style.animationIterationCount =  animation.infinite ? (isDebug ? 1 : 'infinite') : animation.interationCount
          			$el.style.animationDelay =  `${animation.delay}s`
          			$el.style.animationFillMode =  'both'
          			let resolveFn = function(){
          				$el.removeEventListener('animationend', resolveFn, false);
          				$el.addEventListener('animationcancel', resolveFn, false);
          				resolve()
          			}
          			$el.addEventListener('animationend', resolveFn, false)
          			$el.addEventListener('animationcancel', resolveFn, false);
          		})
          	}
          	for(let i = 0, len = animationList.length; i < len; i++){
          		await playFn(animationList[i])
          	}
          	if(callback){
          		callback()
          	}
          }
          復制代碼

          animationIterationCount 如果是編輯模式的化動畫只執(zhí)行一次,不然無法監(jiān)聽到動畫結束animationend事件

          執(zhí)行動畫前先將元素樣式style緩存起來,當動畫執(zhí)行完再將原樣式賦值給元素

          let cssText = this.$el.style.cssText;
          runAnimations(this.$el, animations, true, () => {
          	this.$el.style.cssText = cssText
          })
          復制代碼

          元素添加事件

          提供事件mixins混入到組件,每個事件方法返回promise,元素被點擊時按順序執(zhí)行事件方法

          頁面插入js腳本

          參考百度H5,將腳本以script標簽形式嵌入。頁面加載后執(zhí)行。 這里也可以考慮mixins方式混入到頁面或者組件,可根據業(yè)務需求自行擴展,都是可以實現的。

          redo/undo歷史操作紀錄

          1. 歷史操作紀錄存在狀態(tài)機store.state.editor.historyCache數組中。
          2. 每次修改編輯操作都把整個pageDataJson字段push到historyCache
          3. 點擊redo/undo時根據index獲取到pageDataJson重新渲染頁面

          psd設計圖導入生成h5頁面

          將psd每個設計圖中的每個圖層導出成圖片保存到靜態(tài)資源服務器中,

          服務端安裝psd依賴

          cnpm install psd --save
          復制代碼

          加入psd.js依賴,并且提供接口來處理數據

          var PSD = require('psd');
          router.post('/psdPpload',async ctx=>{
          	const file = ctx.request.files.file; // 獲取上傳文件
          	let psd = await PSD.open(file.path)
          	var timeStr = + new Date();
          	let descendantsList = psd.tree().descendants();
          	descendantsList.reverse();
          	let psdSourceList = []
          	let currentPathDir = `public/upload_static/psd_image/${timeStr}`
          	for (var i = 0; i < descendantsList.length; i++){
          		if (descendantsList[i].isGroup()) continue;
          		if (!descendantsList[i].visible) continue;
          		try{
          			await descendantsList[i].saveAsPng(path.join(ctx.state.SERVER_PATH, currentPathDir + `/${i}.png`))
          			psdSourceList.push({
          				...descendantsList[i].export(),
          				type: 'picture',
          				imageSrc: ctx.state.BASE_URL + `/upload_static/psd_image/${timeStr}/${i}.png`,
          			})
          		}catch (e) {
          			// 轉換不出來的圖層先忽略
          			continue;
          		}
          	}
          	ctx.body = {
          		elements: psdSourceList,
          		document: psd.tree().export().document
          	};
          })
          復制代碼

          最后把獲取的數據轉義并返回給前端,前端獲取到數據后使用系統統一方法,遍歷添加統一圖片組件

          • psd源文件大小最好不要超過30M,過大會導致瀏覽器卡頓甚至卡死
          • 盡可能合并圖層,并柵格化所有圖層
          • 較復雜的圖層樣式,如濾鏡、圖層樣式等無法讀取

          html2canvas生成縮略圖

          這里只需要注意下圖片跨域問題,官方提供html2canvas: proxy解決方案。它將圖片轉化為base64格式,結合使用設置(proxy: theProxyURL), 繪制到跨域圖片時,會去訪問theProxyURL下轉化好格式的圖片,由此解決了畫布污染問題。 提供一個跨域接口

          /**
           * html2canvas 跨域接口設置
           */
          router.get('/html2canvas/corsproxy', async ctx => {
          	ctx.body =  await request(ctx.query.url)
          })
          復制代碼

          渲染模板

          實現邏輯

          在engine-template目錄下新建swiper-h5-engine頁面組件,這個組件接收到頁面JSON數據就可以把頁面渲染出來。跟編輯預覽畫板實現邏輯差不多。

          然后使用vue-cli庫打包命令將組件打包成engine.js庫文件。ejs模板引入該頁面組件配合json數據渲染出頁面



          適配方案

          提供兩種方案解決屏幕適配 1、等比例縮放 在將json元素轉換為dom元素的時候,對所有的px單位做比例轉換,轉換公式為 new = old * windows.x / pageJson.width,這里的pageJson.width是頁面的一個初始值,也是編輯時候的默認寬度,同時viewport使用device-width。 2.全屏背景, 頁面垂直居中 因為會存在上下或者左右有間隙的情況,這時候我們把背景顏色做全屏處理

          頁面垂直居中只適用于全屏h5, 以后擴展長頁和PC頁就不需要垂直居中處理。

          模板打包

          package.json中新增打包命令

          "lib:h5-swiper": "vue-cli-service build --target lib --name h5-swiper --dest server/public/engine_libs/h5-swiper engine-template/engine-h5-swiper/index.js"

          執(zhí)行npm run lib:h5-swiper 生成引擎模板js如圖



          頁面渲染

          ejs中引入模板

          <script src="/third-libs/swiper.min.js"></script>

          使用組件

          <engine-h5-swiper :pageData="pageData" />

          后端服務

          初始化項目

          工程目錄上文已給出,也可以使用 koa-generator 腳手架工具生成

          ejs-template 模板引擎配置

          app.js

          //配置ejs-template 模板引擎
          render(app, {
          	root: path.join(__dirname, 'views'),
          	layout: false,
          	viewExt: 'html',
          	cache: false,
          	debug: false
          });
          復制代碼

          koa-static靜態(tài)資源服務

          因為html2canvas需要圖片允許跨域,所以在靜態(tài)資源服務中所有資源請求設置'Access-Control-Allow-Origin':'*'

          app.js

          //配置靜態(tài)web
          app.use(koaStatic(__dirname + '/public'), { gzip: true, setHeaders: function(res){
          	res.header( 'Access-Control-Allow-Origin', '*')
          }});
          復制代碼

          修改路由的注冊方式,通過遍歷routes文件夾讀取文件

          app.js

          const fs =  require('fs')
          fs.readdirSync('./routes').forEach(route=> {
              let api = require(`./routes/${route}`)
              app.use(api.routes(), api.allowedMethods())
          })
          復制代碼

          添加jwt認證,同時過濾不需要認證的路由,如獲取token

          app.js

          const jwt = require('koa-jwt')
          app.use(jwt({ secret: 'yourstr' }).unless({
              path: [
                  /^\/$/, /\/token/, /\/wechat/,
                  { url: /\/papers/, methods: ['GET'] }
              ]
          }));
          復制代碼

          中間件實現統一接口返回數據格式,全局錯誤捕獲并響應

          middleware/formatresponse.js

          module.exports = async (ctx, next) => {
          	await next().then(() => {
          		if (ctx.status === 200) {
          			ctx.body = {
          				message: '成功',
          				code: 200,
          				body: ctx.body,
          				status: true
          			}
          		} else if (ctx.status === 201) { // 201處理模板引擎渲染
          
          		} else {
          			ctx.body = {
          				message: ctx.body || '接口異常,請重試',
          				code: ctx.status,
          				body: '接口請求失敗',
          				status: false
          			}
          		}
          	}).catch((err) => {
          		if (err.status === 401) {
          			ctx.status = 401;
          			ctx.body = {
          				code: 401,
          				status: false,
          				message: '登錄過期,請重新登錄'
          			}
          		} else {
          			throw err
          		}
          	})
          }
          
          復制代碼

          koa2-cors跨域處理

          當接口發(fā)布到線上,前端通過ajax請求時,會報跨域的錯誤。koa2使用koa2-cors這個庫非常方便的實現了跨域配置,使用起來也很簡單

          const cors = require('koa2-cors');
          app.use(cors());
          復制代碼

          連接數據庫

          我們使用mongodb數據庫,在koa2中使用mongoose這個庫來管理整個數據庫的操作。

          • 創(chuàng)建配置文件

          根目錄下新建config文件夾,新建mongo.js

          // config/mongo.js
          const mongoose = require('mongoose').set('debug', true);
          const options = {
              autoReconnect: true
          }
          
          // username 數據庫用戶名
          // password 數據庫密碼
          // localhost 數據庫ip
          // dbname 數據庫名稱
          const url = 'mongodb://username:password@localhost:27017/dbname'
          
          module.exports = {
              connect: ()=> {            
                  mongoose.connect(url,options)
                  let db = mongoose.connection
                  db.on('error', console.error.bind(console, '連接錯誤:'));
                  db.once('open', ()=> {
                      console.log('mongodb connect suucess');
                  })
              }
          }
          復制代碼

          把mongodb配置信息放到config.json中統一管理

          • 然后在app.js中引入
          const mongoConf = require('./config/mongo');
          mongoConf.connect();
          復制代碼

          ... 服務端具體接口實現就不詳細介紹了,就是對頁面的增刪改查,和用戶的登錄注冊難度不大

          啟動運行

          啟動前端

          npm run dev-client
          復制代碼

          啟動服務端

          npm run dev-server
          復制代碼

          注意: 如果沒有生成過引擎模板js文件的,需要先編輯引擎模板,否則預覽頁面加載頁面引擎.js 404報錯

          編譯engine.js模板引擎

          npm run lib:h5-swiper

          .防抖和節(jié)流

          有些頻繁的操作會導致頁面性能和用戶體驗度低,例如: 輸入框搜索會頻繁調端口接口,方法縮小等
          
          (1)防抖-debounce當持續(xù)觸發(fā)事件時,一定時間內沒有再觸發(fā)事件,事件處理函數才會執(zhí)行一次,若設定時間到來之前又一次觸發(fā)事件,就重新開始延時。
          
          const debounce = (fn, delay) => {
              let timer = null;
              return (...agrs)=> {
                  clearTimeout(timer)
                  timer = setTimeout(()=> {
                      fn.apply(this,args)
                  },delay)
              }
          }
          
          (2) 節(jié)流-thottle當持續(xù)觸發(fā)事件時,保證一段時間內只調用一次時間處理函數
          
          const thottle = (fn, delay=500) => {
              let flag =true;
              return (...arg) => {
                  if(!flag) return;
                  flag = false;
                  setTimeout(()=> {
                      fn.apply(this, args);
                      flag = true;
                  },delay)
              }
          }

          2.jsonp原理

          **jsonp跨域的關鍵就在于**:服務端需要在返回的數據外包裹一個在客戶端定義好的回調函數,這樣就在script發(fā)送請求之后就能獲取到數據。
          
          jsonp的缺點
          1.只能get請求,不支持post,put,delete
          2.不安全 xss攻擊
          
          //通過JQuery Ajax 發(fā)起jsonp請求
          (注:不是必須通過jq發(fā)起請求 , 
               例如:Vue Resource中提供 $.http.jsonp(url, [options]))
          $.ajax({
              // 請求方式
              type: "get", 
              // 請求地址
              url: "http://169.254.200.238:8080/jsonp.do", 
              // 標志跨域請求
              dataType: "jsonp",				
              // 跨域函數名的鍵值,即服務端提取函數名的鑰匙(默認為callback)
              jsonp: "callbackparam",   
              // 客戶端與服務端約定的函數名稱
              jsonpCallback: "jsonpCallback",
              // 請求成功的回調函數,json既為我們想要獲得的數據
              success: function(json) {
                  console.log(json);
              },
              // 請求失敗的回調函數
              error: function(e) {
          	alert("error");
              }
          });
          
          jsonp解決跨域的方法:
          <script>
              var script = document.createElement('scritp');
              script.type = 'text/javascript';
              
              script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack';
              document.head.appendChild(script)
              
              // 接受參數的回調函數
              function onBack(res) {
                  console.log(res)
              }
          </script>

          3.vue中的nextTick

          涉及場景: vue生命周期create()鉤子在進行DOM操作時一定要放在Vue.nextTick()回調函數中。
          原因是,在create()鉤子執(zhí)行的時候DOM其實并沒有進行任何渲染,而此時進行DOM操作是徒勞無工的,所以此時一定需要吧js代碼放進Vue.nextTick()的回調函數中。因為該鉤子執(zhí)行的時候所有的DOM都渲染完畢,此時在這個鉤子的回調中進行任何的渲染都是可以的。
          Vue官方解釋:Vue在更新DOM的時候是異步執(zhí)行的,只要監(jiān)聽到數據的變化,Vue將開啟一個隊列,并緩沖在同一事件循環(huán)中發(fā)生的所有數據變更,如果同一個watcher被觸發(fā)多次,只會被推入隊列中一次,這種緩沖時去除重復數據對于避免不必要的計算和DOM操作是必不可少的,然后再下一個時間""tick"中,Vue刷新隊列并執(zhí)行實際(去重后)工作,Vue在內部嘗試使用原生的Promise.then, MutationObserver和setTimediate,如果執(zhí)行環(huán)境不支持,則會采用setTimeout(fn,0)代替。

          4.css選擇器的優(yōu)先級

          1. !importent
          2. 內聯樣式(1000)
          3. id選擇器(100)
          4. class選擇器、屬性選擇器、偽類選擇器(10)
          5. 元素選擇器、關系選擇器、偽元素選擇器 (1)
          6. 通配符(0)

          5.BFC

          BFC全稱塊級格式化上下文(Block Formatting Context),BFC提供了一個獨立的上下文環(huán)境。個環(huán)境中的元素不會影響到其他環(huán)境中的布局,比如浮動元素會形成BFC浮動元素內部子元素主要受浮動元素的影響,兩個浮動元素相互不影響。可以說BFC是一個獨立的容器,這個容器內的布局絲毫不受容器外布局的影響。
          
          觸發(fā)BFC的條件:
              1.根元素或其他包含他的元素
              2.浮動元素(float不為none)
              3.絕對定位元素(position為absolute或fixed)
              4.內聯塊(display: inline-block)
              5.表格單元格(display:table-cell)
              6.表格標題(display:table-caption)
              7.具有overflow且值不為visible
              8.彈性盒:flex或inline-flex
              9.display:flow-root
              10.cloumn-span: all
              
          BFC的約束規(guī)則
              1.內部盒會在垂直方向一個一個的排列(可以看做bfc內部有一個常規(guī)流)
              2.處于同一個bfc中的元素相互影響,可能會出現邊距重疊
              3.每個元素margin box左邊,與容器塊border box左邊相接觸(對于從左往右的格式化,否則相反),即使浮動也是如此。
              4.bfc就是容器上的一個獨立容器,容器內的子元素不會影響到容器外的元素,反之亦然。
              5.計算bfc高度是,考慮容器內包含的所有元素,包括浮動元素。
              6.浮動盒不會疊加到bfc容器上
          BFC可以解決的問題
              1.垂直外邊距重疊問題
              2.去除浮動
              3.自適應兩列布局

          6.盒模型

          盒模型包括了,內容區(qū)域,內填充區(qū)域,邊框區(qū)域,外邊距區(qū)域
          
          實現左邊寬度固定,右邊自適應布局:
          <div class="box">
              <div class="box-left"></div>
              <div class="box-right"></div>
          </div>
          
          1.利用float、margin實現
          .box {
              height: 200px
          }
          .box>div {
              height: 100%
          }
          .box-left {
              float: left,
              width: 200px,
          }
          .box-right {
              margin-left: 200px
          }
          
          2.利用calc計算寬度
          .box {
              height: 200px
          }
          .box>div {
              height: 100%
          }
          .box-left {
              float: left,
              width: 200px,
          }
          .box-right {
              float: right,
              margin-left: calc(100% - 200px)
          }
          
          3.利用float、overflow實現
          .box {
              height: 200px
          }
          .box>div {
              height: 100%
          }
          .box-left {
              float: left,
              width: 200px,
          }
          .box-right {
              overflow: hidden
          }
          
          4.利用flex
          .box {
              height: 200px,
              display: flex
          }
          .box > div {
              height: 100%
          }
          .box-left {
              width: 200px
          }
          .box-right: {
              flex: 1,
              overflow: hidden
          }

          7.緩存

          緩存分為協商緩存,強緩存。強緩存不過服務器,協商緩存需要經過服務器,協商緩存返回的狀態(tài)碼是304。兩種緩存機制可以同時存在,強緩存的優(yōu)先級要高于協商緩存的。
          
          1.強緩存: 瀏覽器不會向服務器發(fā)送任何請求,直接從本地緩存數據中讀取數據并返回狀態(tài)碼200。
              header參數:
              1.Expires:過期時間,若設置了過期時間,則瀏覽器會直接在設置的時間內讀取緩存,不在請求。
              2.Cache-Control: 當值設置為max-age=300,則代表這個請求正確返回的時間5分鐘再次加載資源,則命中強緩存。
              cache-control設置的值:
                  max-age: 用來設置資源可以被緩存的時間。
                  s-maxage:和max-age是一樣額,只是他只針對代理服務器緩存而言。
                  public:指示響應可以被任何緩存區(qū)緩存
                  private: 只針對個人用戶,不會被代理服務器緩存
                  no-cache: 強制客戶端直接向服務器發(fā)送請求,也就是每次請求都必須向服務器發(fā)送,服務器接收到請求,會判斷資源是否變更,是則返回變更的資源,否則返回304,未變更。
                  no-store: 禁止一切緩存
                  
                  
              
          2.協商緩存:向服務器發(fā)送請求,服務器會根據這個請求的request header的一些參數加判斷是否命中協商緩存,若命中,則返回304狀態(tài)碼并帶上新的response header通知瀏覽器從緩存中讀取資源。
              (1)Etag/if-none-match
              etag: 它是由服務器返回給前端的,用來幫助服務器控制web端的緩存驗證
              if-none-match: 當資源過期時,瀏覽器發(fā)現響應頭里面有etag,則再次向服務器請求時帶上if-none-match,服務器收到請求進行對比,決定返回200還是304
              
              (2)Last-Modifed/If-Modifed-Since
              last-modifed: 瀏覽器向服務器發(fā)送資源的最后修改時間
              if-modifed-since: 當資源過期時,發(fā)現響應頭具有l(wèi)ast-modifed聲明,則再次向服務器請求時帶上if-modifed-since,表示請求時間。服務器收到請求后發(fā)現有if-modified-since則與被請求資源的最后修改時間進行對比(Last-Modified),若最后修改時間較新(大),說明資源又被改過,則返回最新資源,HTTP 200 OK;若最后修改時間較舊(小),說明資源無新修改,響應HTTP 304 走緩存

          8.首屏加載優(yōu)化方案

          1.vue-router懶加載
          2.使用cdn加速,將通用的庫從vendor中抽離
              cdn原理:cdn全稱(content delivery network)內容分發(fā)網絡,其目的是通過現有的internet中增加一層新的cache(緩存)層,將網站內容發(fā)布發(fā)布到最接近用戶的網絡"邊緣"的節(jié)點,使用戶可以就近獲取所需的資源,提高用戶訪問網站的響應速度。從技術層面上講,決定于,網絡寬帶的小,用戶訪問量的大,網絡分布不均等原因。
              簡單的說就是將你的源站資源緩存到全球的各個cdn節(jié)點上,用戶獲取資源時從就近的就近的節(jié)點上獲取而不需要每個用戶都從源站上獲取,避免網絡堵塞,緩解源站壓力。
          3.nginx的gzip
          4.vue異步組件
          5.服務端渲染ssr
          6.按需加載ui庫
          7.webpack開啟gzip壓縮
          8.若首屏是登錄頁,可以做成多入口
          9.service worker緩存文件處理(靜態(tài)資源離線緩存)
             "https://blog.csdn.net/screaming_color/article/details/78912396"

          9.Event loop

          Event loop指的是計算機系統的一個運行機制。javascript就采用這種運行機制用來解決單線程的一些問題。
          瀏覽器: 
              1.javascript執(zhí)行線程:負責執(zhí)行js代碼
              2.ui線程:負責ui展示
              3.javascript事件循環(huán)線程: 
              (ps:ui線程不能和javascript線程同時執(zhí)行,可能在操作DOM的時候會沖突),
              
              javascript中的線程都是排隊執(zhí)行,不會并列執(zhí)行,若并列執(zhí)行的話也可能會在例如操作一些dom的時候導致沖突。
              javascript中的任務分為同步任務和異步任務
                  同步任務: 賦值操作,循環(huán)操作,求和運算,表單處理分支語句等
                  異步任務: dom事件,ajax,dom的一些api
              事件循環(huán)機制:javascript的執(zhí)行引擎的主線程從任務列表中獲取任務,若任務是異步任務,則運行到異步任務是會退出主線程,主線程進行下一個任務的獲取。若異步任務處理完成則又會插入到任務列表的末尾,等待主線程處理。當遇到同步任務的時候主線程直接就執(zhí)行了。
          node: 
              node會先開啟一個event loop,當接收到req的時候會把這個任務,會把它關閉然后進行處理,然后去服務下一個請求。當這個請求完成,他就被放回到隊列中,當達到隊列開頭,就將這個任務結果返回給用戶。
              
          瀏覽器和nodejs的 event loop的區(qū)別:
              1.瀏覽器的每個微任務必須在主任務執(zhí)行完畢之后執(zhí)行
              2.node的微任務在各階段之間執(zhí)行。

          10.算法相關

          排序
          (1)冒泡排序: 比較相鄰兩項的值,如果第一項大于第二項則交換位置,元素項向上運動就好像氣泡往上冒一樣。
          
          function bubbleSort(arr) {
              let len = arr.length;
              for(let i = 0; i< len; i++) {
                  for(let j = 0; j< len-1-i; j++) {
                      if (arr[j] > arr[j+1]) {
                          [arr[j+1], arr[j]] = [arr[j], arr[j+1]]
                      }
                  }
              }
              return arr
          }
          
          (2)選擇排序: 首先在排序序列中找到最小(大)的元素,存放到序列的起始位置,然后再從剩余的序列中查找最小(大)的元素,存放到已排序序列的末尾。以此類推直至排序完畢。
          
          function selectSort(arr) {
              let len = arr.length;
              let minIndex, temp;
              
              for(let i=0; i< len-1; i++){
                  minIndex = i;
                  for(let j = i+1; j< len; j++){
                      if(arr[j]< arr[minIndex]) {
                          minIndex = j;
                      }
                  }
                  temp = arr[i];
                  arr[i] = arr[minIndex];
                  arr[minIndex] = temp;
              }
              return arr
          }

          11.網絡安全相關(XXS, CSRF)

          xxs: 跨站腳本攻擊,惡意注入html代碼,其他用戶訪問時會被執(zhí)行,特點:能注入惡意的腳本(html/javascript)代碼到用戶瀏覽的網站上,從未獲取Cookie資料竊取,會話劫持,釣魚等攻擊手段。
          防止手段: 
              1.網站進行嚴格的輸入格式檢測
              2.通過編碼轉義輸出
              3.瀏覽器禁止頁面的js訪問帶有htmlOnly屬性的cookie
          csrf: 攻擊跨站請求偽造,特點:重要操作的所有參數都是可以被攻擊者猜測到的,攻擊者預測到url的所有參數和參數值,才能成功的構成一個偽造請求。
          防止手段:
              1.token驗證機制,請求字段中帶一個token,響應請求時校驗時效性。
              2.用戶操作限制,比如驗證碼
              3.請求來源限制,比如http referer才能完成操作(防御效果相比較差)

          12.webpack優(yōu)化

          打包體積優(yōu)化:
          1.提取第三方庫使用cdn引入第三方庫
          2.代碼壓縮插件uglifyJsPlugin
          3.服務器采用gzip
          4.按需加載資源文件 require.ensure
          5.優(yōu)化devtool中的source-map
          6.剝離css文件,單獨打包
          7.去除不必要的插件,通常是開發(fā)環(huán)境和生產環(huán)境用了同一套配置導致的。
          
          打包效率優(yōu)化:
          1.開發(fā)環(huán)境采用增量構建,啟用熱更新
          2.開發(fā)環(huán)境不做無意義的工作,比如提取css文件的hash等
          3.配置devtool
          4.選擇合適的loader
          5.個別loader開啟cache,比如babel-loader
          6.第三方庫采用引入方式
          7.提取公共代碼
          8.優(yōu)化構建時的搜索路徑,明確需要構建的目錄和不需要構建的目錄
          9.模塊化引入需要的部分

          13.url相關

          url到界面顯示發(fā)生了什么
          1.dns解析(域名解析):本地域名服務器-->根域名服務器-->com頂級域名服務器,一次類推下去。就是先本地緩存查找,再一層一層的查找。將常見的域名地址解析成唯一的ip地址。
          2.tcp連接,三次握手,沒收到信息就重新發(fā)。
              1.主機向服務器發(fā)送一個建立連接的請求。
              2.服務器接收到請求后發(fā)送同意連接的信號
              3.主機接收到同意連接的信號后,再次向服務器發(fā)送確認信號,至此主機與服務器建立了連接。
          3.發(fā)送http請求 瀏覽器會解析url,并設置好請求報文發(fā)出。請求報文中包括,請求頭,請求行,請求體空行。https默認端口403,http默認80。
          4.服務器處理請求并返回http報文
          5.瀏覽器解析渲染頁面
              1.通過html解析器解析html文檔,構建一個dom tree,通過css解析器解析html中存在的css,構建一個style rules,兩者結合構成一個呈現樹(render tree)
              2.render tree 構建完畢進入布局階段,將會為每個階段分配一個出現在屏幕位置上的準確坐標
              3.最后將全部的節(jié)點遍歷繪制出來,一個頁面就展示出來了,當遇到script會暫停下來執(zhí)行script,所以通常吧script放到底部。
          6.結束連接

          14.組件封裝

          封裝組件的目的: 為了復用,提高開發(fā)效率和代碼質量。
          組件封裝應該注意:低耦合,單一職責,可復用性,可維護性
              1.分析布局
              2.初步開發(fā)
              3.化繁為簡
              4.抽象組件

          15.內存泄露

          內存泄露的定義: 程序中已動態(tài)分配的堆內存由于某種原因程序未釋放或無法釋放所引發(fā)的各種問題,js中可能會出現內存泄露的情況。
          結果會導致程序延遲大,程序崩潰
          導致內存泄露的情況:
              1.使用全局變量
              2.dom清空時還在引用
              3.ie中使用閉包
              4.定時器未清理
              5.子元素存在引起的內存泄露
          如何避免:
              1.減少不必要得全局變量,及時對無用的數據進行垃圾回收。
              2.注意程序的邏輯,避免死循環(huán)
              3.避免創(chuàng)建過多的對象
              4.減少層級過多的引用

          16.vue相關

          (1) spa的理解

          spa(single-page application)僅在頁面初始化的時候完成對html,css,js的加載,一旦加載完成,不會再因為用戶的操作再去加載或跳轉,取而代之利用頁面路由來進行頁面之間的切換。
              優(yōu)點:
                  1.用戶體驗好,內容的改變不會重新加載頁面,避免了不必要的渲染和跳轉。
                  2.相對于服務器的壓力較小
                  3.前后端分離架構清晰,前端負責交互邏輯,后盾負責數據處理
              缺點:
                  1.初次加載耗時
                  2.前進后退路由管理:由于所有的頁面都是在一個頁面內顯示的,所以瀏覽器的前進后退按鈕不能使用
                  3.seo難度大:由于所有的內容都是在一個頁面內切換顯示的。

          (2) 什么是mvvm

          mvvm(model-view-viewModel):mvvm是一種軟件架構模式源自mvc,它促進了前端與后端的業(yè)務邏輯分離,極大的提高的前端的開發(fā)效率,它的核心就是viewModel層,負責轉換model中的數據對象讓數據變得更容易管理和使用。向上與視圖層進行雙向數據綁定,向下與model邏輯層驚喜接口請求進行數據交互,起到了承上啟下的作用。

          (3) 雙向數據綁定的實現原理

          https://github.com/Laofu-zhang/ZVue/blob/master/ZVue.js

          (4) v-if、v-show的區(qū)別

          v-if:真正的條件渲染,因為他確保在切換過程中條件塊內的事件監(jiān)聽器和子組件適當的銷毀和重建
          v-show:不管條件是什么,元素總是會被渲染。并且只是簡單的基于css的display屬性進行切換。

          (5) computed和watch的區(qū)別

          computed: 計算屬性,依賴其他的屬性值,并且computed的值有緩存,只有當他依賴的屬性值發(fā)生了改變,下一次獲取到的computed的值時才會被重新計算computed的值。
          watch: 更多的是從當一個觀察的作用,類似于某些數據的監(jiān)聽回調,每當監(jiān)聽到數據變化是都會執(zhí)行回調進行后續(xù)的操作。
          
          應用場景:
          computed:當我們需要進行數值計算,并且依賴于其他的數據時,應該使用computed,因為可以利用computed的緩存特性,避免每次獲取值時都需要重新計算。
          watch:當我們需要在數據變化時執(zhí)行異步或者開銷較大的操作時,應該使用watch。使用watch允許我們執(zhí)行異步操作,限制我們的操作頻率,并在我們得到最終結果前,設置中間狀態(tài),這些都是computed無法做到的。

          (6) vue生命周期

          1.beforeCreate: 組件實例被創(chuàng)造之初,組件屬性生效之前
          2.created:組件實例創(chuàng)建完成,屬性也綁定了,但是真實的dom還沒有生成,$el還不能用。
          3.beforeMount: 在掛載開始之前被調用,相關的render函數被首次調用
          4.mounted:el被新創(chuàng)建的vm.$el替換,并掛載到實例上去之后調用該鉤子
          5.beforeUpdate: 組件更新之前被調用,發(fā)生在虛擬dom補丁之前
          6.updated: 組件更新之后
          7.activited: keep-alive專屬鉤子,組件被激活時調用。
          8.deactivited:keep-alive專屬鉤子,組件被銷毀時調用。
          9.beforeDistory: 組件被銷毀之前。
          10.distoryed: 組件被銷毀之后。

          (7) 那個生命周期異步數據請求最合適

          由于created,beforeMount,mounted三個鉤子中的data都已經創(chuàng)建,可以將服務端返回的數據進行賦值,所以理論上講這三個鉤子都可以請求數據。
          但是created相對來說更好點。
              1.能更快的獲得服務端數據,減少頁面的loading時間
              2.ssr不支持beforeMount,mounted這兩個鉤子,所以放在created中有助于一致性。

          (8) 在什么時候能夠DOM操作

          由于在mounted被調用之前,vue已經將編譯好的模板掛載到了頁面。所以此時可以進行dom操作
          若想要在created中進行dom操作則需要Vue.nextTick()的回調中進行操作。

          (9) vue組件之間的通信方式

          1. 父子組件通信:
              1. props/$emit
              2. ref和$parent/$children(訪問父子實例)
          2. 兄弟組件的通信:
              EventBus($on,$emit)適用于父子,兄弟,隔代組件通信。 這種方法就是通過一個空的Vue實例作為事件總線,用它來觸發(fā)($emit)和監(jiān)聽($on)事件從而時間組件之間的通信。
          3.隔代組件通信:
              1.$attr/$listeners,通過v-bind="$attr",v-on="$listeners"傳入內部組件。
              例子地址:https://juejin.im/post/5cbd29d4f265da03914d608d
              2.provide/inject:祖先組件通過provide來提供變量,子孫組件通過inject來注入變量。
              例子地址: https://juejin.im/post/5c983d575188252d9a2f5bff
          4.vuex 適用于兄弟,父子,隔代組件之間的通信。

          (10) vue組件中的data為什么是一個函數

          因為Object是引用數據類型,如果不用function返回一個object,則每個組件的data引用的都是同一個地址,一個數據改變了,其他的也就都改變了。所以組件內的data必須是一個function返回的object,這樣就避免了污染全局。

          (11) vue中key的作用

          key是為vue中的vnode的唯一標記,通過這個key,我們的diff操作可以更快速,更準確。

          (12) nextTick

          作用: nextTick接受一個回調函數作為參數,它的作用將回調函數延遲到下一次DOM更新周期之后執(zhí)行。
          用途:在視圖更新之后需要對新的視圖進行操作。

          http

          http(超文本傳輸協議),是用于傳輸超文本文檔應用層協議,他是為了web應用層和web服務器的通信而設計的。遵循經典的客戶端-服務端模型,客戶端打開一個連接發(fā)送請求,然后等待他收到服務端的響應,http是一個無狀態(tài)協議,這就意味著服務器不會在兩個請求之間保存任何的數據(狀態(tài))。

          作用域和作用域連

          1.作用域是在運行時代碼中某些特定部分中的變量,函數和對象的可訪問性。也就是說,作用域決定了代碼塊中變量和其他資源的可見性。
          作用域是一個獨立的地盤,讓變量不會外泄,暴露出去。也就是起到了變量隔離的作用,不同作用域中的相同變量不會相互影響。
              1.作用域的分層: 
                  內層的作用域可以訪問外層作用域的變量,外層作用域不能訪問內層作用域的變量。
              2。塊級作用域:
                  1.可通過let const聲明。
                  2.在一個函數內部。
                  3.在一個代碼塊內部。
          2.作用域鏈
              1.自由變量:
              當作用域沒有定義的變量,這稱為自由變量。自由變量的值會從父級作用域中尋找。
              2.作用域鏈:
              若自由變量在父級作用域中沒找到,就還會向父級尋找,一直往上。若找到全局作用域還沒找到,則宣布放棄,這層關系鏈就稱為作用域鏈。

          閉包

          定義:閉包是指有權訪問另一個函數作用域中的變量的函數。從技術的角度講,所有的javascript函數都是閉包:他們都是對象,它們都關聯到作用域鏈。
          
          也就是某個函數在定義的詞法作用域之外被調用,閉包可以使該函數極限訪問定義時的詞法作用域。

          垃圾回收和內存泄露

          程序在運行的時候需要分配內存,所謂的內存泄露就是,不再使用的內存,沒有的到及時釋放,就會造成內存泄露。為了更好的避免內存泄露,我們則就會用到垃圾回收機制。
          
          垃圾回收重要性:由于字符串,對象,數組沒有固定的大小,所以只有當他們的大小已知時才能對他們進行內存的分配,只要想這樣動態(tài)的分配了內存,最終都需要釋放掉這些分配的內存才能夠再次被利用。否則javascript解釋器會消耗掉系統中所有的內存,導致系統崩潰。所以垃圾回收機制尤為重要。
          
          垃圾回收機制
              找出不在使用了的變量,然后釋放其占用的內存,但是此過程不是實時的,應為其開銷較大,所以垃圾回收機制會按照固定的時間間隔周期執(zhí)行。
              垃圾回收的方法:
              1.標記清除
              垃圾收集器會在運行時會給存儲在內存中的變量都叫上標記。當變量進入執(zhí)行環(huán)境的時候,就標記這個變量為執(zhí)行環(huán)境,理論上執(zhí)行環(huán)境的變量的內存永遠不可能被釋放,因為隨時可能被使用。當變量離開的時候就標記為離開環(huán)境,這個時候就能被釋放。
              2.引用計數
              所謂引用計數就是保存在內存中的資源被引用的次數,若一個值得引用次數為0,就表示這個值不被用到,即可釋放內存。
          內存泄露
              造成內存泄露的原因
              1.以外的全局變量
              2.被遺忘的計時器和回調函數
              3.閉包(由于閉包可以維持函數內部的變量,使其得不到釋放)
                  解決辦法:將事件函數定義到外部,解除閉包
              4.未清理DOM元素的引用
              
          避免內存泄露的方法:
              1.減少不必要的全局變量或者生命周期較長的對象,及時對其進行垃圾回收
              2.注意程序邏輯,避免死循環(huán)
              3.避免創(chuàng)建多個對象
          垃圾回收優(yōu)化:
              1.數組長度及時置位0
              2.對象盡量復用,不用的對象置位null
              3.在循環(huán)中的函數表達式盡量放置到循環(huán)外邊

          原型

          原型:是ECMAscript實現繼承的過程中產生的一個概念。
          繼承:繼承是在一個對象的基礎上創(chuàng)建新對象的過程,原型指在這過程中作為基礎的對象。
          new操作符: 可以用構造函數生成一個實例對象,但是有個缺陷,無法做到屬性和方法的共享。
          prototype:考慮到構造函數不能共享屬性的特點,為了解決此問題出現了prototype這個屬性。所有實例對象需要共享的屬性和方法都放到這個prototype下面,不需要的則放到構造函數里面。
          原型鏈:原型鏈是通過Object.create()和.prototype時生成的一個__proto__的一個指針來實現的。
              訪問:優(yōu)先在對象本身查找,沒有則順著原型鏈向上查找。
              修改:只能修改和刪除自身屬性,不會影響到原型鏈上的其他對象。
          總結: 由于所有的實例對象共享了一個prototype對象,那么從外界看起來,prototype對象就好像是實例對象的原型,而實例對象則好像繼承了prototype對象一樣。

          new操作符

          new操作符的作用:
          實現一個new:
          1. new操作符會返回一個對象,所以需要在函數內部創(chuàng)建的一個新的對象。
          2. 這個對象,也就是構造函數中的this,可以訪問到掛載在this上的任意值。
          3. 這個對象可以訪問到構造函數原型上的屬性。所以需要對象與夠著函數連接起來
          4.返回原始值需要忽略,返回對象需要正常處理。
          
          function create(Con,...args) {
              let obj = {}
              Object.setPrototypeOf(obj, Con.prototype)
              let result = Con.apply(obj, args)
              return result instanceof Object ? result : obj
          }

          js事件循環(huán)

          js是一門單線程非阻塞的腳本語言,也就是說只有一個線程來執(zhí)行任務,非阻塞的意思就是說當代碼需要執(zhí)行異步任務的時候,主線程就會掛起,當異步任務執(zhí)行完成以后,主線程會根據一定的規(guī)則去執(zhí)行回調。
          事實上,當任務完畢時,js將這個事件加入一個隊列(事件隊列)。被放入這個隊列的時間不會立刻執(zhí)行,而是等待當前執(zhí)行棧中所有的任務執(zhí)行完畢后,主線程回去查找事件隊列中是否有任務。
          任務又分為宏任務和微任務,不同類型的任務會被分配到不同的任務隊列中。
          執(zhí)行棧中的所有任務執(zhí)行完畢之后,主線程會去查找事件回調中的任務,如果存在,則依次執(zhí)行直至任務為空。然后再去宏任務隊列中取出一個事件,把對應的回調加入到當前執(zhí)行棧。當前執(zhí)行棧中所有任務執(zhí)行完畢,檢查微任務隊列事件是否有任務,無限循環(huán)此過程,就稱為事件循環(huán)。

          webpack中l(wèi)oader和plugins的區(qū)別

          loader: 對模塊的源代碼進行轉換。
          plugins: 是用來擴展webpack的功能的,主要通過構建流程里注入鉤子實現,plugins是為了完成webpack所不能實現的復雜功能

          AMD CMD commonjs三者區(qū)別

          AMD:amd是requireJs在推廣過程中產生的產物。它的規(guī)范規(guī)則是非同步加載模塊,允許指定回調函數。
              標準的api: require([module], callback) define(id, [depends], callback)
              test.js
              define(['package/lib'], function(){
                  function foo() {
                      lib.log('hello world')
                  }
              })
              require([test.js], function(test){
                  test.foo()
              })
          CMD: CMD是seaJs在推廣過程中對模塊化定義的規(guī)范產出,他是同步模塊定義
          所有的模塊都通過define來定義
              define(function(require, exports, module){
                  var $ =require('jquery')
                  var test = require('test.js')
                  exports.doSomthing = {}
                  modules.exports = {}
              })
          commonJs規(guī)范: 目前nodejs使用此規(guī)范,它的核心思想是通過require來同步加載所依賴的其他模塊然后通過export和module.exports導出暴露。
          ES6: export/import來進行導出導入

          數組方法總結

          includes:判斷數組中是否存在某個元素
          forEach:遍歷數組,沒有返回值且不會改變原數組
          map:會返回一個新數組但不會改變原數組,默認返回undefined
          find:根據檢索條件,查找出第一個滿足該條件的值,若找不到返回undefined
          findIndex:根據檢索條件,查找出滿足該條件的值得下標,若找不到返回undefined
          filter:過濾數組,返回新數組但不會改變原數組
          push,pop:數組末尾的追加和刪除
          unshift,shift:數組頭部添加和刪除元素
          concat:數組后面拼接一個新元素。該方法不會改變原數組,會返回新拼接好的數組
          reverse:數組反轉,返回一個新數組,且改變原數組
          sort:數組排序
          join,split:數組轉字符串,字符串轉數組
          every:判斷數組中每一項都滿足設定條件,則返回true
          some:數組中只要有一項滿足設定條件,則返回true
          indexOf,lastIndexOf:兩個方法都是用來查找索引,接受兩個產生,第一個是查找的對象,第二個值是起始位置。找到了返回索引值,沒找到返回-1
          slice:數組截取接受兩個參數,開始位置和結束位置。不改變原數組
          splice:從數組中添加和刪除數組只,會改變原數組
          reduce:arr.reduce((prev, current, index, array) => {}, initialValue),為數組中每一個元素一次執(zhí)行回調函數,不包括被刪除的和為賦值的元素

          對象常用方法總結

          Object.assign: 用于對象合并,此方法實行淺拷貝。第一個參數時合并的目標對象,后面的參數是合并的對象
          Object.create: 使用指定原型去創(chuàng)建一個新的對象
          Object.defineProperties:直接在對象上定義新屬性或修改屬性第一個值是目標對象,第二個值時屬性,第三個值是屬性描述
          Object.keys: 返回一個由自身可枚舉屬性組成的數組
          Object.values: 返回對象自身可枚舉屬性值
          Object.entries: 返回對象自身可枚舉屬性鍵值對數組
          hasOwnProperty: 判斷自身屬性中是否有指定屬性
          Object.getOwnPropertyDescriptor(obj, prop): 返回指定對象上的屬性描述
          Object.getOwnPropertyDescriptors(obj): 返回一個對象所有自身屬性的描述
          Object.getOwmPropertyNames: 返回一個對象所有自身屬性的屬性名,包括可枚舉和不可枚舉的
          Object.getPrototypeOf: 返回指定對象的原型
          isPrototypeOf: 判斷一個對象是否在另一個對象的原型連上
          Object.setPrototypeOf(obj, prototype):設置對象的原型
          Object.is: 判斷兩個對象是否相同
          Object.freeze: 凍結一個對象
          Object.isFrozen: 判斷一個對象是否被凍結

          iterator遍歷過程:

          1.創(chuàng)建一個指針對象,指向當前數據結構的起始位置。也就是說,遍歷起對象的本質就是一個指針對象
          2.第一次調用指針對象的next方法,可以將指針指向數據結構的第一個成員
          不斷調用指針對象的next方法直至它指向數據結構的結束位置。

          瀏覽器兼容性的處理

          樣式方面兼容:
          由于各個瀏覽器廠商的內核不同所以得樣式添加前綴
              1. ie trident -ms
              2. fireFox gecko -moz
              3. opera presto -o
              4. chrome和safari webkit -webkit

          ajax

          let xhr = new XMLHttpRequest();
          xhr.onreadystatechange = (callback)=> {
              if (xhr.readyState === 4){
                  if ((xhr.state >= '200' && xhr.state <= '300') || xhr.state === 304) {
                      callbakc(xhr.reaponseText)
                  } else {
                      console.error('error')
                  }
              }
          }
              
          xhr.open('get', 'exmaple.json', true)
          xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
          xhr.send()

          web storage cookie session的區(qū)別


          上一篇:JavaScript 返回頂部實例
          下一篇:HTML 鏈接
          主站蜘蛛池模板: 精品一区二区视频在线观看| 国产乱码一区二区三区四| 亚洲综合一区无码精品| 国产伦一区二区三区高清| 91精品一区二区三区久久久久 | 亚洲国产精品一区二区久久hs| 2021国产精品视频一区| 91视频国产一区| 精品国产一区二区三区色欲| 91国偷自产一区二区三区| 日韩精品无码一区二区三区免费 | 久久精品午夜一区二区福利| 亚欧免费视频一区二区三区| 91香蕉福利一区二区三区| 亚洲色一区二区三区四区| 在线电影一区二区| 手机看片一区二区| 免费无码一区二区三区| 人妻在线无码一区二区三区| 无码精品人妻一区| 亚洲AV日韩AV天堂一区二区三区| 国产高清在线精品一区| 尤物精品视频一区二区三区 | 国产精品亚洲一区二区三区 | 国产一区二区成人| 一区二区三区四区视频在线| 亚洲欧洲一区二区| 日本一区视频在线播放| 亚洲天堂一区二区| 视频在线一区二区| 久久精品无码一区二区三区免费| 日韩精品一区二区午夜成人版| 精品无码一区二区三区在线 | 亚洲日韩精品一区二区三区无码 | 亚洲国产成人久久一区二区三区 | 乱人伦一区二区三区| 国产福利一区二区三区| 久久se精品一区精品二区| 亚洲熟女综合一区二区三区| 激情内射亚洲一区二区三区| 亚洲一区二区三区无码影院|