Promise的几个api用法

Promise 应该大家都很熟悉了。大部分的库也都用上了Promise。
这文章主要记录下 Promise.all、Promise.allSettled、Promise.race 这几个的用法。

Promise.all:
参数是一个数组,数组元素是各个Promise。要数组里面所有的 Promise 都返回成功,那这个 Promise.all 的结果才会是成功。成功返回数组,就是元素成功的结果集合。失败的话返回单值,是元素里第一个失败的结果。

Promise.allSettled:
跟 Promise.all 参数一样,是一个数组,数组元素是各个Promise。不同的是这个不会因为数组元素的成败来返回成功失败。返回的是个数组,记录着元素 Promise 的各个结果,包括成败。

先准备四个小函数,然后再混起来用:

function PromiseSuccess0() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('PromiseSuccess0')
    }, 0)
  })
}
function PromiseSuccess1000() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('PromiseSuccess1000')
    }, 1000)
  })
}
function PromiseError2000() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('PromiseError2000')
    }, 2000)
  })
}
function PromiseError3000() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('PromiseError3000')
    }, 3000)
  })
}

Promise.all([PromiseSuccess0(), PromiseError3000(), PromiseError2000()])
 .then((data) => {
   console.log('success all-1: ', data)
 })
 .catch((error) => {
   console.error('error all-1: ', error)
    // error all-1:  PromiseError2000
 })

Promise.all([PromiseSuccess0(), PromiseSuccess1000()])
 .then((data) => {
   console.log('success all-2: ', data)
    // success all-2:  (2) ["PromiseSuccess0", "PromiseSuccess1000"]
 })
 .catch((error) => {
   console.error('error all-2: ', error)
 })

Promise.allSettled([PromiseError2000(), PromiseSuccess1000()])
  .then((data) => {
    console.log('success allSettled: ', data)
    // success allSettled:  
    // (2) [{…}, {…}]
    // 0: {status: "rejected", reason: "PromiseError2000"}
    // 1: {status: "fulfilled", value: "PromiseSuccess1000"}
  })
  .catch((error) => {
    console.error('error allSettled: ', error)
  })

Promise.race
顾名思义,竞赛。参数依然是个元素为 Promise 的数组。会返回第一个跑完的元素结果。话不多说,上码:

function load1000() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('load1000')
    }, 1000)
  })
}
function load3000() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('load3000')
    }, 3000)
  })
}
function errorTimer() {
 return new Promise((resolve, reject) => {
   setTimeout(() => {
     reject(new Error('timeout'))
   }, 2000)
 })
}

Promise.race([load1000(), errorTimer()])
 .then((data) => {
   console.log('success race-1: ', data)
   // success race-1:  load1000
 })
 .catch((error) => {
   console.error('error race-1: ', error)
 })

Promise.race([load3000(), errorTimer()])
 .then((data) => {
   console.log('success race-2: ', data)
 })
 .catch((error) => {
   console.error('error race-2: ', error)
   // error race-2:  Error: timeout
 })

具体的使用场景可以是,比如下载个东西,给其设定个时间限定,超过这个时间就报错。稍微封装一下:

function addTimer(fn, time) {
 const errorTimer = new Promise((resolve, reject) => {
   setTimeout(() => {
     reject(new Error('timeout'))
   }, time)
 })
 return Promise.race([fn(), errorTimer])
}

addTimer(load1000, 2000)
 .then((data) => {
   console.log('success addTimer: ', data)
 })
 .catch((error) => {
   console.error('error addTimer: ', error)
 })

webpack通过webpackJsonp来连接多文件依赖

webpack一般是打成一份文件,但有时也需要打成几份,生成的文件可以把其中的公共部分抽取出来,省点代码。
这里就免不了依赖文件顺序的问题。这里用了很巧妙的方法,来连接不同的文件,以及依赖的前置性问题。
相当用心思,这里稍微记录下。

先准备两份文件 foo.js,bar.js,util.js:

// foo.js
import util from './util'

console.log('foo')
console.log(util('foo'))

// bar.js
import util from './util'

console.log(util('bar'))
console.log('bar')

export default function () {
    return 'bar-function'
}

// util.js
console.log('util')

export default function (flag) {
    return 'util' + flag
}

webpack配置文件:

const { execSync } = require('child_process')
var webpack = require('webpack');

execSync(`rm -rf build`)

module.exports = {
    mode: 'development',
    devtool: 'none',//配置生成Source Maps,选择合适的选项
    entry:  { 
        foo: __dirname + "/foo.js",
        bar: __dirname + "/bar.js"
    },//已多次提及的唯一入口文件
    output: {
        path: __dirname + "/build",//打包后的文件存放的地方
        filename: "[name].js"//打包后输出文件的文件名
    },
     optimization: {
        splitChunks: {
          chunks: 'all',
          minSize: 1,
          maxSize: 0,
          minChunks: 1,
          maxAsyncRequests: 6,
          maxInitialRequests: 4,
          automaticNameDelimiter: '~',
          automaticNameMaxLength: 30,
          cacheGroups: {
            vendors: {
              test: /[\\/]node_modules[\\/]/,
              priority: -10
            },
            default: {
              minChunks: 2,
              priority: -20,
              reuseExistingChunk: true
            }
          }
        }
      }
}

会在 dist 文件夹生成三个文件:foo.js,bar.js,defaultbarfoo.js
看得出 default-bar-foo.js 就是共用代码,这里就是 util.js,代码是:

// window["webpackJsonp"]是个数组,push了一个数组。元素1还是一个数组,里面只有一个值"default~bar~foo"
// 其实就是这份文件名字,或者说是这份chunk名字
// 而第二个值就又是各个module的集合。比如这里只有一个util.js,结合chunk名就很明显了,
// util.js就是foo.js和bar.js共同用到的。如果还有其他共用到的,那么还有多几个字段
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["default~bar~foo"],{
/***/ "./util.js":
/*!*****************!*\
  !*** ./util.js ***!
  \*****************/
/*! exports provided: default */
      // __webpack_exports__=module.exports
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
console.log('util')
/* harmony default export */ __webpack_exports__["default"] = (function (flag) {
    return 'util' + flag
});
/***/ })
}]);

如果平时有看过 webpack 生成的代码的话,就能很明显看出与入口文件打出来的内容很不一样,缺少了webpackBootstrap
看看 foo.js,(bar.js也长得差不多),几个名词先说一下:

module:每一份文件的js代码。比如a.js引入了b.js,把a.js作为entry。那么到时打出来的就是只有一份a.js(该输出文件就是一份chunk,输出文件名字可变)。里面有两个模module,a.js和b.js。
chunk:要输出的每一份文件就是一个chunk(一个或多个module组成一份chunk),一般是一个entry对应一个chunk。如果像这种抽离公共js的,也是一个chunk,也会多于entry数。

foo.js是
一个自执行的函数,传入一个json对象。只有一个key值,是”./foo.js”。相对应的value其实是一个函数模块,就是foo.js的代码内容。
打包出来的js靠全局变量维系,window[“webpackJsonp”]。在每个entry chunk的最上面都有webpack加载代码,而附属chunk则没有,只有看起来像数组push的代码,如default-bar-foo.js文件所示。

/******/ (function(modules) { // webpackBootstrap
/******/ 	// install a JSONP callback for chunk loading
                    // 这data就是window["webpackJsonp"] push进来的东西
/******/ 	function webpackJsonpCallback(data) {
/******/ 		var chunkIds = data[0];// chunkId="default~bar~foo"
/******/ 		var moreModules = data[1];// {'./util.js':f}
/******/ 		var executeModules = data[2]; // 暂不知何用
/******/
/******/ 		// add "moreModules" to the modules object,
/******/ 		// then flag all "chunkIds" as loaded and fire callback
/******/ 		var moduleId, chunkId, i = 0, resolves = [];
/******/ 		for(;i < chunkIds.length; i++) {
/******/ 			chunkId = chunkIds[i];
/******/ 			if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
                // resolves暂不知作何用
/******/ 				resolves.push(installedChunks[chunkId][0]);
/******/ 			}
              // 标记chunk安装
/******/ 			installedChunks[chunkId] = 0;
/******/ 		}
/******/ 		for(moduleId in moreModules) {
/******/ 			if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
                // 把每个chunk的每份module记录在modules,用以调用运行
/******/ 				modules[moduleId] = moreModules[moduleId];
/******/ 			}
/******/ 		}
            // 当有parentJsonpFunction这方法适合,就执行这个方法,参数为每个chunk代码
            // window["webpackJsonp"]里面的元素其实就是非入口chunk代码块
            // parentJsonpFunction取自上一个window["webpackJsonp"]的push的方法,
            // 当第一个script的入口chunk,它的parentJsonpFunction就是普通数组的push
            // 同时在第一个script的入口chunk,把window["webpackJsonp"]的push改写为第一个script的入口chunk的webpackJsonpCallback方法
            // 也就是第二个script的入口chunk里,这个parentJsonpFunction就是第一个script的入口chunk的webpackJsonpCallback方法
            // 这样就把两份chunk的代码通过这种方式联系起来
            // 当push(改写后的push)了非入口chunk,可以通过这种联系一层层传递到各个chunk的webpackJsonpCallback方法
            // webpackJsonpCallback里面再调用checkDeferredModules方法,来检查并执行相应的模块代码
            // 这样一来无论script顺序怎样,只要调用webpackJsonpCallback方法,就能达到加载的模块(chunk即为模块合集)传递到各个入口chunk
/******/ 		if(parentJsonpFunction) parentJsonpFunction(data);
/******/
/******/ 		while(resolves.length) {
/******/ 			resolves.shift()();
/******/ 		}
/******/
/******/ 		// add entry modules from loaded chunk to deferred list
            // 看原注释是添加新的前置依赖
            // 暂不知为何是添加executeModules而不是一开始就写在deferredModules
/******/ 		deferredModules.push.apply(deferredModules, executeModules || []);
/******/
/******/ 		// run deferred modules when all chunks ready
/******/ 		return checkDeferredModules();
/******/ 	};
          // 检查依赖到齐没有,并执行
          // 在主流程里会运行一次
          // 在webpackJsonpCallback里最后再运行一次,也就是每次有chunk的加载都会检查运行一次
/******/ 	function checkDeferredModules() {
/******/ 		var result;
/******/ 		for(var i = 0; i < deferredModules.length; i++) {
/******/ 			var deferredModule = deferredModules[i];
/******/ 			var fulfilled = true;
/******/ 			for(var j = 1; j < deferredModule.length; j++) {
                // depId="./foo.js","default~bar~foo"
/******/ 				var depId = deferredModule[j];
                // 是否已安装记录在installedChunks
/******/ 				if(installedChunks[depId] !== 0) fulfilled = false;
/******/ 			}
              // 全部安装完毕
/******/ 			if(fulfilled) {
                // 把 deferredModules 清空
/******/ 				deferredModules.splice(i--, 1);
                // deferredModule[0]本chunk的名字,这里是"./foo.js"
/******/ 				result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
/******/ 			}
/******/ 		}
/******/
/******/ 		return result;
/******/ 	}
/******/
          // 先看同步代码,从这里开始看起
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// object to store loaded and loading chunks
/******/ 	// undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ 	// Promise = chunk loading, 0 = chunk loaded
          // 执行到这里,代表本身这个chunk也就是这个文件已加载
          // 这里同时也会标记别的chunk加载了没有,来判断前置依赖有了没有
/******/ 	var installedChunks = {
/******/ 		"foo": 0
/******/ 	};
/******/
/******/ 	var deferredModules = [];
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
            // 每个加载的模块结果缓存起来
            // 这里每个module就是未打包前的每一份js
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
            // 这里注意到具体模块去看,会把这个模块的导出值赋值到module.exports
            // 然后再 return 出去,所以 __webpack_require__ 模块会返回加载运行模块的结果
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
            // 标记这个 module 加载过
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/  // 部分 __webpack_require__.字段 这里未体现作用
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ 		}
/******/ 	};
/******/
/******/ 	// define __esModule on exports
          // 打上 ES6 
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 		}
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// create a fake namespace object
/******/ 	// mode & 1: value is a module id, require it
/******/ 	// mode & 2: merge all properties of value into the ns
/******/ 	// mode & 4: return value when already ns object
/******/ 	// mode & 8|1: behave like require
/******/ 	__webpack_require__.t = function(value, mode) {
/******/ 		if(mode & 1) value = __webpack_require__(value);
/******/ 		if(mode & 8) return value;
/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ 		var ns = Object.create(null);
/******/ 		__webpack_require__.r(ns);
/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ 		return ns;
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
          // 初始化window["webpackJsonp"]为一个数组
/******/ 	var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
          // 把旧的window["webpackJsonp"]的push方法缓存起来,当然第一个就是数组的push方法
/******/ 	var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
          // 改写window["webpackJsonp"]的push方法为webpackJsonpCallback
/******/ 	jsonpArray.push = webpackJsonpCallback;
/******/ 	jsonpArray = jsonpArray.slice();
          // 把window["webpackJsonp"]的chunk传入webpackJsonpCallback执行一下
/******/ 	for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
          // 这里再次把旧的window["webpackJsonp"]的push方法赋值到parentJsonpFunction
          // parentJsonpFunction会存在在这个chunk的作用域,在这个chunk的webpackJsonpCallback里被调用
/******/ 	var parentJsonpFunction = oldJsonpFunction;
/******/
/******/
/******/ 	// add entry module to deferred list
                    // 首位为本身chunk,后面为所依赖的chunk
                    // 所以可以得知靠着installedChunks和deferredModules这两个变量来判断到底所需的文件加载好了没有
                    // 以至于在script标签里的引入顺序会干扰到依赖加载从而影响执行
          // 注意这里是二维数组
/******/ 	deferredModules.push(["./foo.js","default~bar~foo"]);
/******/ 	// run deferred modules when ready
/******/ 	return checkDeferredModules();
/******/ })
/************************************************************************/
/******/ ({

/***/ "./foo.js":
/*!****************!*\
  !*** ./foo.js ***!
  \****************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
// 标记为 ES6 模块
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _util__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./util */ "./util.js");

console.log('foo')
console.log(Object(_util__WEBPACK_IMPORTED_MODULE_0__["default"])('foo'))

/***/ })

/******/ });

通过全局的 webpackJsonp 这个数组来连接多份文件。改写了这个数组的 push 方法。让其在每次 push 的时候,都一层一层找上之前加载过的文件。
触发每份入口文件里的检查依赖(checkDeferredModules),来判断前置依赖是否准备好。好则开始执行。

vue的nextTick解析

用过 vue 的同学应该都知道,双向绑定中,更改了数据会去更新dom,但不是马上更新的。
直接跟在修改数据后,拿到的 dom 还是旧的。
vue有个this.$nextTick用法,用这个的回调可以保证拿到更新后的dom。

直接看看 next-tick 源码

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'

export let isUsingMicroTask = false
const callbacks = []
let pending = false

// 清异步队列,全部执行
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}
let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  // 判断有没有 promise 且有没有被重写过,
  // promise完好无损就使用 promise 做异步队列的触发,设置使用微任务标志为true
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  // 特殊场景用MutationObserver
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  // Use MutationObserver where native Promise is not available,
  // e.g. PhantomJS, iOS7, Android 4.4
  // (#6466 MutationObserver is unreliable in IE11)
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  // Fallback to setImmediate.
  // Techinically it leverages the (macro) task queue,
  // but it is still a better choice than setTimeout.
  // 再不济看看有没有 setImmediate,虽然也是宏任务,但总比 setTimeout 强
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  // Fallback to setTimeout.
  // 实在没办法了,setTimeout 兜底
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}
// nextTick 具体操作所在,其他队列方法也是用这个,比如 data 改动后通知 dom 修改,也是用这个
// 即 vue 里面的响应式,不是实时更新的,而是存在一个队列,在下一回合进行任务更新。不然太耗损性能
// 也就是解释了,在修改了 data 之后,dom 是没有马上更新,而在 $nextTick 的回调后,则可以看到 dom 更新
// 原因就在于同样的异步任务,$nextTick 的回调,是在 dom 修改的操作后面,所以 $nextTick 能看到 dom 更新
// 以下以 this.$nextTick(()=>{}) 为例子,cb 为回调,ctx 为 vue 实例
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  // 把任务塞进异步队列
  callbacks.push(() => {
    // 有回调就触发回调
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      } 
      // 这里注意,结合下面来看
      //用 promise 的 resolve 触发,把 vue 实例当成参数传进去
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  // 在清任务 flushCallbacks 里赋值 pending 为 false
  // 目的为了 timerFunc 在周期内只触发一次
  // 执行 timerFunc,让其下一周期执行清异步队列任务
  if (!pending) {
    pending = true
    timerFunc()
  }
  // 当没有回调且 Promise 正常
  // $nextTick 返回的是一个 promise,触发时机与上面有回调一样
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

除了常规的回调用法之外,还可以这样用

this.$nextTick().then( vue实例 => { console.log(vue实例 === this) }) // => true

如果不传入回调,则返回promise,因为是 promise,也可以直接就 await 等待。参考官网

接着来个简陋版简单模拟一下这个 nextTick 的实现。这其中还涉及到事件循环的知识。

// 模拟vue nextTick
// 存储所有$nextTick的回调,变量的set修改后通知dom更新的,与$nextTick的回调进入同个微队列
const callbacks = []
// 微队列的触发载体,这里是Promise.resolve()
// vue是先找Promise.resolve,没有就找setTimeout
let run = null

function next() {
  callbacks.forEach((cb) => {
    cb()
  })
}
// 这里的change可以看成修改了变量,里面的回调就是与nextTick的回调如出一辙
function change() {
  nextTick(() => {
    // 这里面是同步
    console.log('update dom -- 1')
    const child = document.createElement('div')
    child.id = 'new'
    document.getElementById('box').appendChild(child)
    console.log('update dom')
    console.log('update dom -- 2')
  })
}

function nextTick(cb) {
  if(!run) {
    // 初始化下个队列
    run = Promise.resolve()
    // 把next方法放在下个队列运行
    // 看上面的next函数,是把callbacks的函数遍历执行
    run.then(next)
  }
  // nextTick的回调加入数组等着,遍历触发里面的每个函数
  // 由于是同步,所以其实只是一个微任务,
  callbacks.push(cb)
}

// 调用了第一次nextTick
nextTick(() => {
  // 表示该次微队列任务开始
  console.log('nextTick -- 1')
})
Promise.resolve().then(() => {
  console.log('then -- ')
})
// 修改变量,更改了dom
change()
// 同步任务,最早打印,但此时没有new元素
console.log('script -- ', document.getElementById('new'))
// 调用了第二次nextTick
nextTick(() => {
  // 在修改变量之后调nextTick,已有new元素
  console.log('nextTick -- 2', document.getElementById('new'))
})
// nextTick -- 2 之所以在 then -- 前打印,是因为回调都放在callbacks里,同步触发了


垂直居中的几个方法

四种方法,先在外面放四个容器, 赋予 “.container” 类。让其子元素居中,先赋予 “.box” 类,水平居中。

<!DOCTYPE>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>baidu</title>
</head>
<style type="text/css">
.container {
  width: 22%;
  height: 300px;
  float: left;
  background-color: #abcdef;
  margin: 0 10px;
  position: relative;
}
.box {
  width: 100px;
  height: 100px;
  background-color: red;
  text-align: center;
}
<!-- 绝对定位大法,设置为绝对定位,四个方向位移为0,外边距设为auto -->
.middle1 {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  margin: auto;
}
<!-- flex弹性布局,特别容易完成这个事 -->
.middle2 {
  display: flex;
  justify-content: center;
  align-items: center;
}
<!-- 利用表格居中,注意的是还要多一层table-cell,真正居中是在table-cell -->
.middle3 {
  display: table-cell;
  vertical-align: middle;
  background-color: initial;
}
.block {
  display: inline-block;
}
<!-- 绝对定位加translate大法,水平垂直位移50%,translate里面的50%是相对于自己的,所以不知宽高也可以使用 -->
.middle4 {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translateX(-50%) translateY(-50%);
}
</style>
<body>
<div class="container"><div class="box middle1"></div></div>
<div class="container middle2"><div class="box"></div></div>
<div class="container" style="display: table;">
  <div class="box middle3">
    <p class="box block"></p>
  </div>
</div>
<div class="container"><div class="box middle4"></div></div>
</body>
</html>

总结:个人觉得,首先 flex 布局最方便也最帅气。然后绝对定位加 translate 也好理解。至于第一种绝对定位加四个方向位移为0,其实不太理解其原理~ = =!

vue生命周期

对于 vue 的几个生命周期老是记不住,应该还是理解不够透彻,这里记录一下。

代码与注释参考了 segmentfaultimooc 这两篇文章,感谢。

<!DOCTYPE>
<html>
<head>
  <title></title>
  <script type="text/javascript" src="https://cdn.jsdelivr.net/vue/2.1.3/vue.js"></script>
</head>
<body>
<div id="app">
  <p>{{ message }}</p>
  <a @click="change">change</a>
  <a @click="destroy">destroy</a>
  <span v-if="false"></span>
</div>
<script type="text/javascript">
var i = 0
var app = new Vue({
  // render > template > el,但 el 不能缺少
  el: '#app',
  // template: '<div id="app">{{message1}}</div>',
  // render: function(createElement) {
  //   const menu_items = ["首页","搜索","分类","系统"]
  //   return createElement('ul', 
  //     menu_items.map(item => {
  //       return createElement('li', {
  //         class: {
  //           'uk-nav': true
  //         },
  //         domProps: {
  //           innerHTML: item
  //         }
  //       })
  //     })
  //   )
  // },
  data: {
    message : 'init',
    message1 : 'ok'  
  },
  methods: {
    change () {
      this.message = 'ok' + i++
    },
    destroy () {
      this.$destroy()
    }
  },
  beforeCreate: function () {
    // 创建实例前,此时虚拟dom和属性全都拿不到
    // 主要就是给vm对象添加了 $parent、$root、$children 属性,以及一些其它的生命周期相关的标识
    // 初始化事件相关的属性
    // vm 添加了一些虚拟 dom、slot 等相关的属性和方法
    console.info('beforeCreate 创建前状态===============》')
    console.log('%c%s', 'color:red' , 'el     : ' + this.$el) //undefined
    console.log('%c%s', 'color:red','data   : ' + this.$data) //undefined 
    console.log('%c%s', 'color:red','message: ' + this.message) //undefined 
  },
  created: function () {
    // 初始化,可以拿到属性,应该是完成了数据劫持,但dom依旧拿不到
    // props、methods、data、watch、computed等数据初始化
    console.info('created 创建完毕状态===============》')
    console.log('%c%s', 'color:red','el     : ' + this.$el) //undefined
    console.log('%c%s', 'color:red','data   : ' + JSON.stringify(this.$data)) //已被初始化 
    console.log('%c%s', 'color:red','message: ' + this.message) //已被初始化
  },
  beforeMount: function () {
    // 根据el和template属性来初始化dom
    console.info('beforeMount 挂载前状态===============》')
    console.log('%c%s', 'color:red','el     : ' + (this.$el)) //已被初始化
    // 这里的dom还没进行模板替换,也就是还显示着 {{message}} 这种占位符,span 元素也还在
    // 这里就算有 template render,也是出现 outer html
    console.log(this.$el)
    console.log('%c%s', 'color:red','data   : ' + this.$data) //已被初始化  
    console.log('%c%s', 'color:red','message: ' + this.message) //已被初始化 
  },
  mounted: function () {
    // 完成挂载,{{message}} 被数据替换,span 元素也被移除
    console.info('mounted 挂载结束状态===============》')
    console.log('%c%s', 'color:red','el     : ' + this.$el) //已被初始化
    console.log(this.$el)    
    console.log('%c%s', 'color:red','data   : ' + this.$data) //已被初始化
    console.log('%c%s', 'color:red','message: ' + this.message) //已被初始化 
  },
  beforeUpdate: function () {
    console.info('beforeUpdate 更新前状态===============》')
    console.log('%c%s', 'color:red','el     : ' + this.$el)
    // 这里的dom还是旧的
    console.log(this.$el)   
    console.log('%c%s', 'color:red','data   : ' + JSON.stringify(this.$data))
    // 数据已更新,,这里修改数据会引起死循环
    console.log('%c%s', 'color:red','message: ' + this.message)
  },
  updated: function () {
    console.info('updated 更新完成状态===============》')
    console.log('%c%s', 'color:red','el     : ' + this.$el)
    // dom已更新,这里修改数据会引起死循环
    console.log(this.$el) 
    console.log('%c%s', 'color:red','data   : ' + this.$data) 
    console.log('%c%s', 'color:red','message: ' + this.message)
  },
  beforeDestroy: function () {
    // 所有东西还在
    console.info('beforeDestroy 销毁前状态===============》')
    console.log('%c%s', 'color:red','el     : ' + this.$el)
    console.log(this.$el)    
    console.log('%c%s', 'color:red','data   : ' + this.$data) 
    console.log('%c%s', 'color:red','message: ' + this.message)
    this.message = 'destroy'
  },
  destroyed: function () {
    // 数据绑定被卸除,监听被移出,子实例也统统被销毁,dom保留着
    console.info('destroyed 销毁完成状态===============》')
    console.log('%c%s', 'color:red','el     : ' + this.$el)
    console.log(this.$el)  
    console.log('%c%s', 'color:red','data   : ' + this.$data) 
    console.log('%c%s', 'color:red','message: ' + this.message)
    setTimeout(() => {
      // 实例还在
      console.log('vue 实例===============》', app)
    }, 1000)
  }
})
</script>
</body>
</html>