歲月不居,時節如流。轉眼之間2019悄然而過,回首2019,我們豪情滿懷,漫天風雪風景好;展望2020,我們重任在肩,陽光普照寫佳績。舟至中流需奮進,風好正是揚帆時。
在2019最后一天里,可能你還想做這么的一件事,給客戶發一份精美的郵件祝福,但卻無從下手,怎么辦?拉易網能幫到你,準備好素材后,只需一分鐘就能生成精美的HTML郵件格式。話不多說,直接上視頻。
一分鐘制作精美HTML郵件格式的元旦祝福郵件
走著走著…
2019已接近尾聲
一眨眼就是一天
一回頭就是一年
一轉身就是一輩子
人生總有太多的來不及
微信小程序跳轉方式
1.navigator 跳轉
最常見的跳轉方法就是運用<navigator url="../../.."></navigator>進行跳轉,只要在url中添加跳轉頁面的路徑即可。
代碼
<navigator url="../skill/skill">
........//跳轉涵蓋內部所有代碼形成的頁面
</navigator>
1
2
3
2.運用 bindtap在js中實現頁面的跳轉
比起navigator的跳轉,個人更喜歡用bindtap跳轉,運用更靈活,也不用去除點擊樣式。
bindtap='home'(home位置隨便替換)
代碼
//wxml
<view bindtap='home'>
</view>
//js
home: function (e) {
wx.navigateTo({
url: '../../..' //跳轉鏈接
})
}
**重點
在開發的過程中,突然遇到以上兩種方式都無法實現跳轉,也不會報錯的情況,經過查詢資料,發現內部調用wx.switchTab可以很好的解決這一現象
代碼
home: function (e) {
wx.switchTab({
url: '../../..'
})
}
一、前言
我們都知道,vue組件中通信是用props進行父子通信,或者用provide和inject注入的方法,后者類似與redux的porvider,父組件使用,包含在里面的子組件都可以使用,provide/inject用法看我的博客(provide/inject用法),provide/indect官方文檔,不過provide/indect一般用的不多,都是用前者,但是props有一個問題,父傳子沒問題,但是子后面還有子,子后面還有子,子子孫孫無窮盡也,所以這就要一層層的傳下去,太麻煩了,所以vuex就派上用場了,vuex作為一個很輕量的狀態管理器,有著簡單易用的的API接口,在任意組件里面都能使用,獲取全局狀態,統一獲取改變,今天,就看一下源碼怎么實現的。
二、準備
1)先去github上將vuex源碼下載下來。
2)打開項目,找到examples目錄,在終端,cd進入examples,執行npm i,安裝完成之后,執行node server.js
3)執行上述之后,會得到下方的結果,表示編譯完成,打開瀏覽器 輸入 localhost:8080
4) 得到頁面,點擊counter
5)最終,我們就從這里出發調試吧。
三、調試
找到counter目錄下的store.js 寫一個debugger,瀏覽器打開F12,刷新頁面,調試開始。
1.Vue.use()
先進入Vue.use(Vuex),這是Vue使用插件時的用法,官方文檔也說了,Vue.use,會調用插件的install方法,所以如果你要寫插件的話,你就要暴露一個install方法,詳情請看vue官方文檔之use的用法
即use會調用下方的方法(debugger會進入)
function initUse (Vue) {
Vue.use = function (plugin) {
var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
var args = toArray(arguments, 1);
args.unshift(this);
if (typeof plugin.install === 'function') {
// 如果插件的install是一個function,調用install,將 this指向插件,并將Vue作為第一個參數傳入
// 所以調用vuex吧this指向vuex,并吧vue當參數傳入
plugin.install.apply(plugin, args);
} else if (typeof plugin === 'function') {
plugin.apply(null, args);
}
installedPlugins.push(plugin);
return this
};
}
1.1 vuex的install方法
在源碼目錄的src目錄下的store.js文件里最下方有個install函數,會調用它
debugger進入后
export function install (_Vue) { // _Vue是上面說的Vue作為第一個參數 ,指針this指向Vuex
if (Vue && _Vue === Vue) {
// 如果你在開發環節,你使用了兩次Vue.use(Vuex) 就會報下方的錯誤,提醒你vue只能被use一次,可以自行試試
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue) // 調用applyMixin方法
}
1.2 vuex的applyMixin方法
applyMixin方法在mixin.js文件里 同樣在src目錄下
export default function (Vue) {
const version = Number(Vue.version.split('.')[0]) // 獲取vue的版本
if (version >= 2) { // vue的版本是2.xx以上 執行vue的mixin混入,該函數不懂用法請查看官方文檔,
// mixin合并混入操作,將vuexInit 方法混入到beforeCreate生命周期,意思就是當執行beforeCreate周期的時候
// 會執行vuexInit 方法
Vue.mixin({ beforeCreate: vuexInit })
} else { // 1.xxx版本太老了 現在基本不用,暫不講解,可以自行了解
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
/*
Vuex init hook, injected into each instances init hooks list.
/
function vuexInit () {
// 因為該方法是在beforeCreate下執行,而beforeCreate的this指向為Vue 所以this === Vue
// 獲得vue里面的option配置,這里涉及到vue的源碼,以后再講vue ,
//所以這就是我們為什么要在new Vue的時候,傳遞一個store進去的原因,
//因為只有傳進去,才能在options中獲取到store
/
var vm = new Vue({
el: "#app",
data() {return{}},
store
})
*/
const options = this.$options
// store injection
if (options.store) {
// 如果options對象中store有,代表是root ,如果options.store是函數,執行調用options.store()
// 如果是對象直接options.store 賦值給this.$stroe
// 這也就是我們為什么能夠在Vue項目中直接this.$store.disptach('xxx')的原因,從這里獲取
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
// 如果options.store為空,則判斷options.parent.$store有沒有 從父元素判斷有沒有store,
//從而保證子元素父元素公用一個store實例
this.$store = options.parent.$store
}
}
}
1.3 Vue.use(Vuex)總結
至此,Vue.use(Vuex)全部分析完成,總結,就是Vue.use調用Vuex的install的方法,然后install使用mixin混入beforecreate生命周期中混入一個函數,當執行生命周期beforecreate的時候回執行vuexInit 。你可以慢慢調試,所以要好好利用下方的調試按鈕,第二個按鈕執行下一步,第三個進入方法,兩個配合使用。
2.new Vuex.Store
Vue.use(Vuex)已經結束,再回到counter目錄下的store.js文件
export default new Vuex.Store({
state,
getters,
actions,
mutations
})
debugger進入,Vuex.Store方法在src目錄下的store.js文件下
export class Store {
constructor (options = {}) {
// 這里的options是在counter定義的 state,getters,actions,mutations當做參數傳進來
// Auto install if it is not done yet and window
has Vue
.
// To allow users to avoid auto-installation in some cases,
// this code should be placed here. See #731
if (!Vue && typeof window !== 'undefined' && window.Vue) {
// 掛載在window上的自動安裝,也就是通過script標簽引入時不需要手動調用Vue.use(Vuex)
install(window.Vue)
}
if (process.env.NODE_ENV !== 'production') {
// 開發環境 斷言,如果不符合條件 會警告,這里自行看
assert(Vue, must call Vue.use(Vuex) before creating a store instance.
)
assert(typeof Promise !== 'undefined', vuex requires a Promise polyfill in this browser.
)
assert(this instanceof Store, store must be called with the new operator.
)
}
const {
plugins = [],
strict = false
} = options
// store internal state
//下方是在Vuex的this上掛載一些對象,這里暫且不要知道他們要來干什么
// 因為是源碼分析,不要所有的代碼都清除,第一次源碼分析,你就只當他們是掛載對象,往下看
this._committing = false
this._actions = Object.create(null)
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
// ModuleCollection代表模塊收集,形成模塊樹
this._modules = new ModuleCollection(options) //碰到第一個不是定義空對象,debugger進去,分析在下面
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()
this._makeLocalGettersCache = Object.create(null)
// bind commit and dispatch to self
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) { // 綁定dispatch的指針為store
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) { // 綁定commit的指針為store
return commit.call(store, type, payload, options)
}
// strict mode
this.strict = strict
const state = this._modules.root.state // 獲取到用戶定義的state
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
// 初始化root模塊 注冊getters,actions,mutations 子模塊
installModule(this, state, [], this._modules.root)
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
// 初始化vm 用來監聽state getter
resetStoreVM(this, state)
// apply plugins
// 插件的注冊
plugins.forEach(plugin => plugin(this))
// 下方的是開發工具方面的 暫不提
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
}
}
2.1 new ModuleCollection
ModuleCollection函數在src目錄下的module目錄下的module-collection.js文件下
export default class ModuleCollection {
constructor (rawRootModule) { // rawRootModule === options
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
}
2.1.1 register()
register (path, rawModule, runtime = true) {
// register([],options,false)
if (process.env.NODE_ENV !== 'production') {
assertRawModule(path, rawModule) // 開發環境斷言,暫忽略
}
const newModule = new Module(rawModule, runtime)
if (path.length === 0) { // path長度為0,為根節點,將newModule 賦值為root
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested modules
if (rawModule.modules) { // 如果存在子模塊,遞歸調用register,形成一棵樹
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2.1.2 new Module()
Module函數在同目錄下的modele.js文件下
export default class Module {
// new Module(options,false)
constructor (rawModule, runtime) {
this.runtime = runtime
// Store some children item
this._children = Object.create(null)
// Store the origin module object which passed by programmer
this._rawModule = rawModule // 將options放到Module上 用_raModele上
const rawState = rawModule.state // 將你定義的state取出
// Store the origin module's state
// 如果你定義的state為函數,調用函數,為對象,則取對象 賦值為module上的state上
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
}
所以Module的this內容為如下:
2.1.3 installModule
function installModule (store, rootState, path, module, hot) {
// installModule(Vuex,state,[],module) module是前面 new ModuleCollection產生的對象
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// register in namespace map
if (module.namespaced) {
if (store._modulesNamespaceMap[namespace] && process.env.NODE_ENV !== 'production') {
console.error([vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}
)
}
store._modulesNamespaceMap[namespace] = module
}
// set state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
if (process.env.NODE_ENV !== 'production') {
if (moduleName in parentState) {
console.warn(
[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"
)
}
}
Vue.set(parentState, moduleName, module.state)
})
}
// 設置當前上下文
const local = module.context = makeLocalContext(store, namespace, path)
// 注冊mutation
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 注冊action
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
// 注冊getter
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
// 逐一注冊子module
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
2.1.4 makeLocalContext
// 設置module的上下文,綁定對應的dispatch、commit、getters、state
function makeLocalContext (store, namespace, path) {
const noNamespace = namespace === ''
const local = {
//noNamespace 為true 使用原先的 至于后面的 不知道干啥用的 后面繼續研究
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
if (!options || !options.root) {
type = namespace + type
if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
console.error([vuex] unknown local action type: ${args.type}, global type: ${type}
)
return
}
}
return store.dispatch(type, payload)
},
commit: noNamespace ? store.commit : (_type, _payload, _options) => {
//noNamespace 為true 使用原先的
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
if (!options || !options.root) {
type = namespace + type
if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {
console.error([vuex] unknown local mutation type: ${args.type}, global type: ${type}
)
return
}
}
store.commit(type, payload, options)
}
}
// getters and state object must be gotten lazily
// because they will be changed by vm update
Object.defineProperties(local, {
getters: {
get: noNamespace
? () => store.getters
: () => makeLocalGetters(store, namespace)
},
state: {
get: () => getNestedState(store.state, path)
}
})
return local
}
function getNestedState (state, path) {
return path.reduce((state, key) => state[key], state)
}
2.1.5 registerMutation
mutation的注冊,會調用下方方法,將wrappedMutationHandler函數放入到entry中
function registerMutation(store, type, handler, local) {
// entry和store._mutations[type] 指向同一個地址
var entry = store._mutations[type] || (store._mutations[type] = []);
entry.push(function wrappedMutationHandler(payload) {
handler.call(store, local.state, payload);
});
}
2.1.6 forEachAction
action的注冊,會調用下方方法,將wrappedActionHandler函數放入到entry中
function registerAction(store, type, handler, local) {
var entry = store._actions[type] || (store._actions[type] = []);
// entry和store._actions[type]指向同一個地址
entry.push(function wrappedActionHandler(payload) {
var res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload);
if (!(0, _util.isPromise)(res)) {
res = Promise.resolve(res);
}
if (store._devtoolHook) {
return res.catch(function (err) {
store._devtoolHook.emit('vuex:error', err);
throw err;
});
} else {
return res;
}
});
}
因為entry和上面的_action[type],_mutations[type] 指向同一個地址,所以:
2.1.7 forEachGetter
getter的注冊,會調用下方方法
function registerGetter(store, type, rawGetter, local) {
if (store._wrappedGetters[type]) {
if (true) {
console.error('[vuex] duplicate getter key: ' + type);
}
return;
}
store._wrappedGetters[type] = function wrappedGetter(store) {
return rawGetter(local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
);
};
}
2.1.8 resetStoreVM
function resetStoreVM (store, state, hot) {
const oldVm = store._vm //將_vm用變量保存
// bind store public getters
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters // 獲取installModule方法完成的_wrappedGetters內容
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
// 攔截get返回store._vm[key]中的值,即可以通過 this.$store.getters.xxx訪問值
get: () => store._vm[key],
enumerable: true // for local getters
})
})
// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
const silent = Vue.config.silent
// silent設置為true,取消所有日志警告等
Vue.config.silent = true
store._vm = new Vue({ // 將state,getter的值進行監聽,從這里就可以看出getter其實就是采用的vue的computed
data: {
$$state: state
},
computed
})
// 恢復原先配置
Vue.config.silent = silent
// enable strict mode for new vm
if (store.strict) {
enableStrictMode(store)
}
// 若存在舊的實例,解除對state的引用,等dom更新后把舊的vue實例銷毀
if (oldVm) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
看到這里,你應該對vuex有初步的了解
// 這也是我們為什么能用訪問到state,getter的原因
//this.store.dispatch('xxx') ,this.$store.dispatch('xxx')
1
2
相信你也有點亂,其實上面很多我沒講到的不是我不想講,是具體我也不知道干啥的,看源碼學習呢,看主要就行,后面理解多了,其他的就慢慢都會,你不可能剛開始看,就每一行,他寫的每一句的用途都知道是干什么的,只能先看主要方法,在慢慢琢磨,一步步來吧,別急,現在我來畫一個流程圖,更好的去理解吧。
2.1.9 流程圖
3.連貫
import Vue from 'vue'
import Counter from './Counter.vue'
import store from './store'
new Vue({
el: '#app',
store,
render: h => h(Counter)
})
當運行new Vue的時候,傳入了store,前面minix beforecreate,執行到beforecreate鉤子時,會調用vueInit函數,就可以在this.$store取到store對象了,因為options.store有值了 ,不為空,這樣就連貫到了,所以這就是為什么可以用this.$store取值。
HTML常用meta大全
Meta標簽是HTML語言head區的一個輔助性標簽,它位于HTML文檔頭部的head標記和title標記之間,它提供用戶不可見的信息。
Meta : 即 元數據(Metadata)是數據的數據信息。
元數據可以被使用瀏覽器(如何顯示內容或重新加載頁面),搜索引擎(關鍵詞),或其他 Web 服務調用。
用我們的大白話來說,它本身是一個沒什么用的標簽,但是一旦在它內部通過其他屬性設置了某些效果,它就起作用了,所以我們稱之為“ 元數據 ”。
Code
<!-- 定義文檔的字符編碼 -->
<meta charset="utf-8" />
<!-- 強制Chromium內核,作用于360瀏覽器、QQ瀏覽器等國產雙核瀏覽器 -->
<meta name="renderer" content="webkit"/>
<!-- 強制Chromium內核,作用于其他雙核瀏覽器 -->
<meta name="force-rendering" content="webkit"/>
<!-- 如果有安裝 Google Chrome Frame 插件則強制為Chromium內核,否則強制本機支持的最高版本IE內核,作用于IE瀏覽器 -->
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"/>
<!--
設置視窗大小
width 設置layout viewport 的寬度,為一個正整數,或字符串"width-device"
initial-scale 設置頁面的初始縮放值,為一個數字,可以帶小數
minimum-scale 允許用戶的最小縮放值,為一個數字,可以帶小數
maximum-scale 允許用戶的最大縮放值,為一個數字,可以帶小數
shrink-to-fit=no IOS9中要想前面的屬性起作用需要加上這個
height 設置layout viewport 的高度,這個屬性對我們并不重要,很少使用
user-scalable 是否允許用戶進行縮放,值為"no"或"yes", no 代表不允許,yes代表允許
-->
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<!-- 頁面描述 -->
<meta name="description" content="不超過150個字符"/>
<!-- 頁面關鍵詞 -->
<meta name="keywords" content=""/>
<!-- 網頁作者 -->
<meta name="author" content="name, email@gmail.com"/>
<!--
搜索引擎抓取
all:文件將被檢索,且頁面上的鏈接可以被查詢;
none:文件將不被檢索,且頁面上的鏈接不可以被查詢;
index:文件將被檢索;
follow:頁面上的鏈接可以被查詢;
noindex:文件將不被檢索;
nofollow:頁面上的鏈接不可以被查詢。
-->
<meta name="robots" content="index,follow"/>
<!-- 忽略頁面中的數字識別為電話,忽略email識別-->
<meta name="format-detection" content="telphone=no, email=no"/>
<!-- IOS begin -->
<!-- 添加到主屏后的標題(iOS 6 新增) -->
<meta name="apple-mobile-web-app-title" content="標題">
<!-- 當網站添加到主屏幕快速啟動方式,可隱藏地址欄,僅針對ios的safari (ios7.0版本以后,safari上已看不到效果) -->
<meta name="apple-mobile-web-app-capable" content="yes"/>
<!-- 是否啟用 WebApp 全屏模式,刪除蘋果默認的工具欄和菜單欄 -->
<meta name="apple-touch-fullscreen" content="yes"/>
<!-- 添加智能 App 廣告條 Smart App Banner(iOS 6+ Safari) -->
<meta name="apple-itunes-app" content="app-id=myAppStoreID, affiliate-data=myAffiliateData, app-argument=myURL">
<!-- 設置蘋果工具欄顏色:默認值為 default(白色),可以定為 black(黑色)和 black-translucent(灰色半透明) -->
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<!-- 不讓百度轉碼 -->
<meta http-equiv="Cache-Control" content="no-siteapp" />
<!-- 針對手持設備優化,主要是針對一些老的不識別viewport的瀏覽器,比如黑莓 -->
<meta name="HandheldFriendly" content="true">
<!-- 微軟的老式瀏覽器 -->
<meta name="MobileOptimized" content="320">
<!-- uc強制豎屏 -->
<meta name="screen-orientation" content="portrait">
<!-- QQ強制豎屏 -->
<meta name="x5-orientation" content="portrait">
<!-- UC強制全屏 -->
<meta name="full-screen" content="yes">
<!-- QQ強制全屏 -->
<meta name="x5-fullscreen" content="true">
<!-- UC應用模式 -->
<meta name="browsermode" content="application">
<!-- QQ應用模式 -->
<meta name="x5-page-mode" content="app">
<!-- windows phone 點擊無高光 -->
<meta name="msapplication-tap-highlight" content="no">
<!--
iOS 圖標 begin
網站添加至ios桌面時的圖標
-->
<!-- iPhone 和 iTouch,默認 57x57 像素,必須有 -->
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="table_ico57.png">
<!-- Retina iPhone 和 Retina iTouch,114x114 像素,可以沒有,但推薦有 -->
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="table_ico72.png">
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="table_ico114.png">
<!-- Retina iPad,144x144 像素,可以沒有,但推薦有 -->
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="table_ico144.png">
<!-- iOS 啟動畫面 begin -->
<!-- iPad 豎屏 768 x 1004(標準分辨率) -->
<link rel="apple-touch-startup-image" sizes="768x1004" href="/splash-screen-768x1004.png"/>
<!-- iPad 橫屏 1024x748(標準分辨率) -->
<link rel="apple-touch-startup-image" sizes="1024x748" href="/Default-Portrait-1024x748.png"/>
<!-- iPad 豎屏 1536x2008(Retina) -->
<link rel="apple-touch-startup-image" sizes="1536x2008" href="/splash-screen-1536x2008.png"/>
<!-- iPad 橫屏 2048x1496(Retina) -->
<link rel="apple-touch-startup-image" sizes="2048x1496" href="/splash-screen-2048x1496.png"/>
<!-- iPhone/iPod Touch 豎屏 320x480 (標準分辨率) -->
<link rel="apple-touch-startup-image" href="/splash-screen-320x480.png"/>
<!-- iPhone/iPod Touch 豎屏 640x960 (Retina) -->
<link rel="apple-touch-startup-image" sizes="640x960" href="/splash-screen-640x960.png"/>
<!-- iPhone 5/iPod Touch 5 豎屏 640x1136 (Retina) -->
<link rel="apple-touch-startup-image" sizes="640x1136" href="/splash-screen-640x1136.png"/>
<!-- IOS end -->
<!-- Windows 8 磁貼顏色 -->
<meta name="msapplication-TileColor" content="#000"/>
<!-- Windows 8 磁貼圖標 -->
<meta name="msapplication-TileImage" content="icon.png"/>
<!-- 添加 RSS 訂閱 -->
<link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml"/>
<!-- sns 社交標簽 begin -->
<!-- 參考微博API -->
<meta property="og:type" content="類型" />
<meta property="og:url" content="URL地址" />
<meta property="og:title" content="標題" />
<meta property="og:image" content="圖片" />
<meta property="og:description" content="描述" />
<!-- sns 社交標簽 end -->
低版本IE瀏覽器訪問問題
添加好強制Webkit內核的代碼,使用國產瀏覽器訪問網站已經不存在IE兼容問題了,IE訪客量將大大減少。但仍然不可避免會有一些老舊電腦通過低版本IE瀏覽器訪問,如果我們專門為了這極小部分用戶進行 CSS HACK ,將嚴重加重我們的工作量。
更何況自2016年1月起微軟已經停止為IE11以下版本提供支持和更新,已經這么久沒有更新,低版本IE不僅對CSS3和HTML5支持有問題,更有安全問題。
那么,我們不去支持低版本IE,這小部分用戶怎么辦呢?
我們可以使用 if IE 語句給網站添加IE升級提示,提示用戶進行瀏覽器升級,或者切換更先進的瀏覽器再訪問。
我們可以在剛剛的meta標簽下添加一段跳轉到IE升級提示頁的代碼(當IE版本低于IE11時跳轉),實現低版本IE用戶訪問時提示他們進行升級或者更換瀏覽器。
強制Webkit內核和提示低版本IE訪問用戶升級完整代碼如下所示,把這段代碼添加到頭部模板文件標簽下即可:
<meta name="renderer" content="webkit"/>
<meta name="force-rendering" content="webkit"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<script>/@cc_on window.location.href="); @/</script>
1
2
3
4
(@cc_on 是 IE10 及更舊版IE特有的條件編譯語句,因此可以用來判斷是否除 IE11 以外的其他IE版本。)
因為低版本IE訪問時因為不兼容CSS3和HTML5網站往往是錯版的,添加了上面這段代碼,當低版本IE用戶訪問時就會跳轉到升級提示頁,避免不必要的資源加載,降低網站服務器開銷。
測試代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<meta name="renderer" content="webkit"/>
<meta name="force-rendering" content="webkit"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<script>/@cc_on window.location.href="); @/</script>
</head>
<body>
<h1>Hello world</h1>
</body>
</html>
IE 11 會正常輸出
IE 10 將會看到以下頁面
參考
前端 Meta 用法大匯總 - MR_LIXP
通過meta代碼強制瀏覽器使用WebKit內核極速模式(解決 meta name=“renderer” content=“webkit” 不起作用)- 艾歡歡
概述
replace的參數是char和CharSequence,即可以支持字符的替換,也支持字符串的替換(CharSequence即字符串序列的意思,說白了也就是字符串)
replaceAll的參數是regex,即基于規則表達式的替換,比如:可以通過replaceAll("\d", “*”)把一個字符串所有的數字字符都換成星號
相同點
相同點:都是全部替換,即把源字符串中的某一字符或字符串全部換成指定的字符或字符串
不同點
不同點:replaceAll支持正則表達式,因此會對參數進行解析(兩個參數均是),如replaceAll("\d", “"),而replace則不會,replace("\d","”)就是替換"\d"的字符串,而不會解析為正則
另外還有一個不同點:“\”在java中是一個轉義字符,所以需要用兩個代表一個。例如System.out.println( “\” ) ;只打印出一個""。但是“\”也是正則表達式中的轉義字符,需要用兩個代表一個。所以:\被java轉換成\,\又被正則表達式轉換成\,因此用replaceAll替換“\”為"\",就要用replaceAll("\","\\"),而replace則replace("\","\")
如果只想替換第一次出現的,可以使用replaceFirst(),這個方法也是基于規則表達式的替換,但與replaceAll()不同的是,只替換第一次出現的字符串
HTTP響應狀態碼
200 OK
201 Created
202 Accepted
203 Non-Authoritative Information
204 No Content
205 Reset Content
206 Partial Content
207 Multi-Status (WebDAV)
226 IM Used (HTTP Delta encoding)
200 OK
請求成功。成功的含義取決于HTTP方法:
GET:資源已被提取并在消息正文中傳輸。
HEAD:實體標頭位于消息正文中。
POST:描述動作結果的資源在消息體中傳輸。
TRACE:消息正文包含服務器收到的請求消息
PUT 和 DELETE 的請求成功通常并不是響應200 OK的狀態碼而是 204 No Content 表示無內容(或者 201 Created表示一個資源首次被創建成功)。
201 Created
表示請求已經被成功處理,并且創建了新的資源。
這通常是在POST請求,或是某些PUT請求之后返回的響應。
新的資源在應答返回之前已經被創建。同時新增的資源會在應答消息體中返回,其地址是原始請求的路徑或者是 Location 首部的值。
202 Accepted
表示服務器端已經收到請求消息,但是尚未進行處理。
但是稍后無法通過 HTTP 協議給客戶端發送一個異步請求來告知其請求的處理結果。該狀態碼適用于將請求交由另外一個進程或者服務器來進行處理,或者是對請求進行批處理的情形。
203 Non-Authoritative Information
表示服務器已成功處理了請求,但返回的實體頭部元信息不是在原始服務器上有效的確定集合,而是來自本地或者第三方的拷貝。
當前的信息可能是原始版本的子集或者超集。例如,包含資源的元數據可能導致原始服務器知道元信息的超集。
使用此狀態碼不是必須的,而且只有在響應不使用此狀態碼便會返回200 OK的情況下才是合適的。
204 No Content
表示該請求已經成功了,但是客戶端客戶不需要離開當前頁面。
默認情況下 204 響應是可緩存的。一個 ETag 標頭包含在此類響應中。
使用慣例是:
在 PUT 請求中進行資源更新,但是不需要改變當前展示給用戶的頁面,那么返回 204 No Content。
如果創建了資源,則返回 201 Created 。
如果應將頁面更改為新更新的頁面,則應改用 200 。
205 Reset Content
用來通知客戶端重置文檔視圖,比如清空表單內容、重置 canvas 狀態或者刷新用戶界面。
與204響應一樣,該響應也被禁止包含任何消息體,且以消息頭后的第一個空行結束。
206 Partial Content
服務器已經成功處理了部分 GET 請求。
類似于 FlashGet 或者迅雷這類的 HTTP 下載工具都是使用此類響應實現斷點續傳或者將一個大文檔分解為多個下載段同時下載。
該請求必須包含 Range 頭信息來指示客戶端希望得到的內容范圍,并且可能包含 If-Range 來作為請求條件。
如果只包含一個數據區間,那么整個響應的 Content-Type 首部的值為所請求的文件的類型,同時包含 Content-Range 首部。
示例:
HTTP/1.1 206 Partial Content
Date: Wed, 15 Nov 2015 06:25:24 GMT
Last-Modified: Wed, 15 Nov 2015 04:58:08 GMT
Content-Range: bytes 21010-47021/47022
Content-Length: 26012
Content-Type: image/gif
... 26012 bytes of partial image data ...
包含多個數據區間,那么整個響應的Content-Type首部的值為multipart/byteranges,其中一個片段對應一個數據區間,并提供 Content-Range 和 Content-Type 描述信息。
示例:
HTTP/1.1 206 Partial Content
Date: Wed, 15 Nov 2015 06:25:24 GMT
Last-Modified: Wed, 15 Nov 2015 04:58:08 GMT
Content-Length: 1741
Content-Type: multipart/byteranges; boundary=String_separator
--String_separator
Content-Type: application/pdf
Content-Range: bytes 234-639/8000
...the first range...
--String_separator
Content-Type: application/pdf
Content-Range: bytes 4590-7999/8000
...the second range
--String_separator--
207 Multi-Status (WebDAV)
代表之后的消息體將是一個XML消息,并且可能依照之前子請求數量的不同,包含一系列獨立的響應代碼。由WebDAV(RFC 2518)擴展的狀態碼.
226 IM Used (HTTP Delta encoding)
服務器已經完成了對資源的 GET 請求,并且響應是對當前實例應用的一個或多個實例操作結果的表示。
在vue項目中,和后臺交互獲取數據這塊,我們通常使用的是axios庫,它是基于promise的http庫,可運行在瀏覽器端和node.js中。他有很多優秀的特性,例如攔截請求和響應、取消請求、轉換json、客戶端防御cSRF等。
一 . 安裝
npm install axios;
1
二 . 引入
在項目的src目錄中,新建一個request文件夾,然后在里面新建一個http.js和一個api.js文件。http.js文件用來封裝我們的axios,api.js用來統一管理我們的接口。
三 . 開始封裝
在http.js中引入axios . vue及其他
import Axios from 'axios'; // 引入axios
import { Message, Loading, Notification } from 'element-ui'
import vue from 'vue';
1
2
3
http.js文件全部代碼如下:
import Axios from 'axios';
import store from '../store';
import { Message, Loading, Notification } from 'element-ui'
import vue from 'vue';
// 環境的切換
if (process.env.NODE_ENV == 'development') {
Axios.defaults.baseURL = "http://10.230.39.58:33390/devops";
}
else if (process.env.NODE_ENV == 'production') {
Axios.defaults.baseURL = "http://10.230.39.58:33390/devops";
}
// request請求攔截器
Axios.defaults.withCredentials = true
vue.prototype.$axios = Axios
//請求超時時間
// Axios.defaults.timeout = 100000;
Axios.defaults.headers.get['Content-Type'] = "application/json"
Axios.interceptors.request.use(config => {
const Basic = sessionStorage.getItem("basicParam")
if (Basic) {
config.headers.Authorization = Basic ${Basic}
;
} else {
console.log("無校驗值");
}
return config;
}, error => {
Promise.reject(error);
})
// respone返回攔截器
Axios.interceptors.response.use(
response => {
if (response.data.code !== 200) {
Notification.error({
title: '錯誤',
message: response.data.message
});
}
return response.data;
}, error => {
// Notification.error({
// title: '錯誤',
// message: '系統異常'
// });
console.log('err' + error);// for debug
return Promise.reject(error);
}
)
export default Axios;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
api.js文件全部代碼如下:
import axios from 'axios';
/
封裝get方法
@param url
@param data
@returns {Promise}
*/
export function fetch(url, params = {}) {
return new Promise((resolve, reject) => {
axios.get(url, {
params: params
})
.then(response => {
resolve(response.data);
})
.catch(err => {
reject(err)
})
})
}
/*
封裝post請求
@param url
@param data
@returns {Promise}
/
export function _post(url, data = {}) {
return new Promise((resolve, reject) => {
axios.post(url, data)
.then(response => {
console.log(response,
"response");
resolve(response);
}, err => {
reject(err)
})
})
}
/
實際業務比較復雜不便展示,寫一個簡單的demo記錄此功能
遍歷此div:
<div v-for="item in demoArray">
<input type="text" v-model="item.name">
<el-switch class="exio-switch" v-model="item.status" active-text="開" inactive- text="關" active-color="#13ce66"></el-switch>
<button @click="showInfo(item)">查看</button>
</div>
js代碼:
new Vue({
el: '#app',
data() {
return {
demoArray: [],
};
},
created() {
// 生成模擬數據
for (let i = 0; i < 5; i++) {
let e = {};
e.name = "div"+i;
e.status = true;
this.demoArray.push(e);
}
},
methods: {
showInfo(item) {
console.log(item.name);
console.log(item.status);
}
}
})
頁面展示:
修改一條數據:
驗證雙向綁定結果:
為了解決不確定數量的數據(數據來源可能是接口等)的展示和操作,將每條數據作為元素放在數組中,通過數組中元素的屬性來進行雙向綁定。
整理的倉促,emmm,收工
項目介紹
本項目總體分為
平臺搭建
模擬數據源生成
實時流數據處理
實時數據大屏
這幾個部分,我將分成幾個博客分別介紹這些部分的工作,本文主要介紹最后一個部分,實時數據大屏。
前面的幾篇文章已經將平臺的搭建,數據模擬生成,流數據處理部分做了詳細的介紹,這篇文章主要是對前面所做的工作進行一個升華,關分析出數據不夠直觀,而能將所做的東西更加直觀的表達出來就需要進行可視化了,下面我將為大家介紹可視化部分的工作
平臺搭建,具體可以看平臺搭建
模擬數據源生成,具體可以看模擬數據源生成
實時流數據處理,具體可以看實時流數據處理
項目下載地址下載
環境介紹
首先還是對環境介紹一下,這部分主要使用的將是html,php,js,css等做網站所需要的一些語言及工具,由于需要進行異步數據加載,所以還需要一個本地的服務器,本文使用的是phpstudy,主要是這個工具還集成了mysql,能簡化不少我們的工作,當然如果自己擁有服務器,那完全是可以將這個部署在服務器上面的
首先我們先要安裝phpstudy,這里不對具體的安裝過程進行介紹,安裝完成后我們可以進入網站的根目錄
在這個目錄下新建一個目錄即可作為我們的網站目錄了
然后我們可以使用phpstudy帶的站點域名管理為我們的網站設置一個域名,其中的網站目就是我們剛剛創建的網站目錄
在hosts里面是需要加入IP和域名的映射的,如:
127.0.0.1 www.sshstudy3.com
1
這樣就可以在瀏覽器里面通過訪問域名來訪問我們要做的網站了
接下來我們需要去創建數據庫,打開phpMyAdmin,我們可以進入數據庫管理界面
這里的賬號密碼默認都是root
進入后我們可以看到如下界面
在這里可以創建數據庫,或者進行數據庫的訪問等,不再贅述
到這里基本需要使用到的環境就基本完成了,接下來就是代碼的部分
首先了解一下遞歸的定義:
遞歸:遞歸函數自己調用自己,且函數內部必須有結束條件、否則就是一個死循環;
遞歸案例:求 n 的階乘 (循環 || 遞歸)
階乘公式先了解一下:
即n的階乘 = n(n-1)的階乘,如歸使用for循環來做這件事件就很簡單:
//for循環
function fact(n) {
let end = 1;
for (var i = 1; i <= n; i++) {
end = i
}
return end
}
console.log(fact(5)) //5的階乘 120
再看看遞歸的做法:
//遞歸
function fact(n) {
if (n === 1) {
return 1 //結束條件
}
return n fact(n - 1) //此處的fact函數相當于當前隊列的階乘
}
console.log(fact(5)) //5的階乘
解析: 公式 n(n-1)! 則函數內部只需要返回 n該函數 n-1,
即 n(n-1)! == nfact(n-1)
看一下內部隊列順序,當形參為5時 階乘為 5 fact(n-1),直至形參n = 1時,fact函數有了返回值 1,有了結束條件后整個函數結束自掉,返回階乘結果。
遞歸的優點:遞歸的實現明顯要比循環簡單得多。
遞歸的缺點:
1、效率低:遞歸由于是函數自己掉自己,而函數調用是有時間和空間的消耗的:每一次函數調用,都需要在內存棧中分配空間以保存參數、返回地址以及臨時變量,而往棧中壓入數據和彈出數據都需要時間。
2、性能差:調用棧可能會溢出,每次函數調用會在內存棧中分配空間,而每個進程的棧的容量是有限的,當調用的層次太多時,就會超出棧的容量,從而導致棧溢出。
總結:對于JavaScript而言,能用循環解決的事情、盡量不要考慮遞歸、 慎用!
藍藍設計的小編 http://www.syprn.cn