Vue 组件通信

一、父组件向子组件通信(Prop)

在子组件里面声明 props 可以接收父组件传过来的值实现父组件向子组件的单向通信。

// 父组件
<son title="后除"></title>

// 子组件
props: {
    titile: {
        type: String,
        required: true
    }
}

二、子组件向父组件通信

1.子组件使用 $emit 触发事件,父组件用 v-on 监听并接收值。

// 父组件
<son @sonEvent="parentListen"></son>

methods: {
    parentListen (msg) {
        console.log(msg)
    }
}

// 子组件
this.$emit('sonEvent', 'I am Mazey!')

2.自定义组件的 v-model

// 父组件
<m-test
    v-model="parentMsg"></m-test>
    {{ parentMsg }}

data: {
    parentMsg: 0
}

// 子组件
<button @click="addsonMsg">addsonMsg</button>
{{ sonMsg }}

data () {
    return {
        sonMsg: 0
    }
},
model: {
    prop: 'sonMsg',
    event: 'sonEvent'
},
methods: {
    addsonMsg () {
        this.$emit('sonEvent', ++this.sonMsg)
    }
}

三、任意组件间通信

注册一个空的 Vue 实例作为中转站,做事件的监听。

// main.js
data: {
    eventHub: new Vue()
}

// 组件A
this.$root.eventHub.$emit('eventName', 'I am Mazey!')

// 组件B
this.$root.eventHub.$on('eventName', msg => {
    console.log(msg)
})

使用 postMessage + iframe 实现跨域通信

一、postMessage

window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机(两个页面的模数 Document.domain设置为相同的值)时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

调用 postMessage() 方法时,向目标窗口派发一个 Event 消息。 该 Event 消息的 data 属性为 postMessage() 的数据;origin 属性表示调用 postMessage() 方法的页面的地址。

二、语法

otherWindow.postMessage(message, targetOrigin)

说明

  • otherWindow:其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行 window.open 返回的窗口对象、或者是命名过或数值索引的 window.frames。
  • message:将要发送到其他 window 的数据。它将会被结构化克隆算法序列化。这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化。
  • targetOrigin:通过窗口的 origin 属性来指定哪些窗口能接收到消息事件,其值可以是字符串 * (表示无限制)或者一个 URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。

三、示例

我配了两个域名 example.mazey.cnexample0.mazey.cn,其中 http://example0.mazey.cn/post-message/send.html 会给 http://example.mazey.cn/post-message/receive.html 发送一条消息 Message From Mazey.

send.html

<script>
  window.onload = function () {
    window.parent.postMessage(
      'Message From Mazey.',
      '*'
    )
  }
</script>

receive.html

<script>
  // 监听
  window.addEventListener(
    'message',
    event => {
      let origin = event.origin || event.originalEvent.origin
      console.log(event, origin, event.data) // MessageEvent{...} "http://example0.mazey.cn" "Message From Mazey."
    }
  )
  // 使用 JS 加载 iframe,保证监听创建后再加载 iframe。
  let iframeEle = document.createElement('iframe')
  iframeEle.src = 'http://example0.mazey.cn/post-message/send.html'
  iframeEle.style = 'border: none;width: 0;height: 0;'
  document.body.insertBefore(iframeEle, document.body.firstChild)
</script>

注意

如果发送的消息有敏感信息(例如:密码),始终验证接受时的 event.origin 和指定发送时的 targetOrigin。

Webpack4.x 入门

概览

新建项目

npm init -y

安装webpack & webpack-cli

(c)npm install -D webpack
(c)npm install -D webpack-cli

# 查看webpack版本
(npx )webpack --version

试打包

src/index.js

document.write('Hello Webpack -Mazey')

dist/index.html

<!doctype html>
<html>
    <head>
        <title>Start Webpack</title>
    </head>
    <body>
        <script src="bundle.js"></script>
    </body>
</html>

webpack.config.js

const path = require('path')

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
}

项目命令行运行:

webpack --config webpack.config.js --mode development

# 出现
Hash: 1ae48c1f7dc49168e983
Version: webpack 4.6.0
Time: 63ms
Built at: 2018-05-03 14:37:02
    Asset      Size  Chunks             Chunk Names
bundle.js  2.84 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./src/index.js] 38 bytes {main} [built]

此时 dist/ 下多了一个 bundle.js 文件, 打开 dist/index.html 出现 Hello Webpack -Mazey

在package.json定制脚本

{
  // ...
  "scripts": {
    "dev": "webpack --config webpack.config.js --mode development",
    "build": "webpack --mode production"
  },
  // ...
}


# or

{
  // ...
  "scripts": {
    "dev": "webpack-dev-server --devtool eval --progress --colors",
    "deploy": "NODE_ENV=production webpack -p"
  },
  // ...
}

然后命令行运行 npm run dev 便等于 webpack --config webpack.config.js --mode development

一、入口[entry]

语法

1.单个入口语法

entry: string|Array<string>

示例:

// ...
entry: './src/index.js'
// ...

# 等于

// ...
entry: {
    main: './src/index.js'
}
// ...

2.对象语法

entry: {[entryChunkName: string]: string|Array<string>}

示例:

// ...
entry: {
    app: './src/app.js',
    vendors: './src/vendors.js'
}
// ....

二、输出[output]

语法

// ...
output: {
    filename: <output filename>,
    path: <path>
}
// ...
  • filename: 用于输出文件的文件名。
  • path: 目标输出目录的绝对路径。

示例:

const path = require('path')

const config = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
}

module.exports = config

若配置多个入口,为保证每个文件具有唯一名称,需要用到占位符

// ...
filename: '[name].js',
// ...

三、模式[mode]

语法

mode: string
  • development: 会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。
  • production: 会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin。

设置 模式 后则不需要在命令后带上 --mode development

四、载入器?[loader]

语法

module: {
    rules: [
      { test: <.*>, use: <loader> },
      { test: <.*>, use: <loader> }
    ]
}
  • test: 标识出应该被对应的 loader 进行转换的某个或某些文件。
  • use: 表示进行转换时,应该使用哪个 loader。

示例:

const path = require('path')

const config = {
  // ...
  module: {
    rules: [
      { test: /\.css$/, use: 'css-loader' }
    ]
  }
}

module.exports = config

碰到“在 require()/import 语句中被解析为 .css 的路径”时,打包之前,先使用 css-loader 转换一下。

五、插件[plugins]

语法

const <PluginName> = require(<plugin-name>)
// ...
plugins: [
    new <PluginName>({
        <attribute>: <value>
    })
]

想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。

ES2017 async函数基本用法

描述

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。more…

语法

async function name([param[, param[, ... param]]]) {
   statements
}

用法

async 一般与 await 搭配使用,在 async 函数内部如果遇到 await 命令,会等到 await 后的 Promise 对象执行完毕才会执行下面的语句。

async function printHello (ms) {
    await timeout(ms)
    console.log('Hello Mazey!')
}
function timeout (ms) {
    return new Promise(r => {
        setTimeout(r, ms)
    })
}
printHello(1000) // 1s后打印 Hello Mazey!

H5 localStorage入门

定义

只读的 localStorage 允许你访问一个 Document 的远端(origin)对象 Storage;数据存储为跨浏览器会话。localStorage 类似于 sessionStorage,区别在于,数据存储在 localStorage 是无期限的,而数据存储在 sessionStorage 会被清除,当页面会话结束时——也就是说当页面被关闭。

属性

length

localStorage 内键值对的数量。

localStorage.length // 0
localStorage.setItem('name', 'mazey')
localStorage.length // 1

方法

1.setItem(key, value)

新增/更新 localStorage 的键值对。

localStorage.setItem('name', 'mazey')
localStorage.setItem('age', '23')
localStorage // Storage {age: "23", name: "mazey", length: 2}

等同于:

localStorage.name = 'mazey'
localStorage.age = '23'
localStorage // Storage {age: "23", name: "mazey", length: 2}

2.getItem(key)

获取 localStorage 中指定键的值。

localStorage.setItem('name', 'mazey')
localStorage.setItem('age', '23')
localStorage.getItem('name') // mazey
localStorage.getItem('age') // 23
localStorage.getItem('sex') // null

等同于:

localStorage.setItem('name', 'mazey')
localStorage.setItem('age', '23')
localStorage.name // mazey
localStorage['age'] // 23
localStorage.sex // undefined

3.removeItem(key)

移除 localStorage 中指定键的键值对。

localStorage.setItem('name', 'mazey')
localStorage.setItem('age', '23')
localStorage // Storage {age: "23", name: "mazey", length: 2}
localStorage.removeItem('age') // undefined
localStorage // {name: "mazey", length: 1}
localStorage.removeItem('age') // undefined

4.clear()

清空 localStorage 中所有键值对。

localStorage.setItem('name', 'mazey')
localStorage.setItem('age', '23')
localStorage // Storage {age: "23", name: "mazey", length: 2}
localStorage.clear()
localStorage // Storage {length: 0}

存取对象(复杂值)

localStorage 只能存字符串,所以数组/对象等复杂值要先用 JSON.stringify() 转换成字符串,取出来时再用 JSON.parse() 转换成复杂值再使用。

let arr = [1, 2, 3]
localStorage.setItem('arr', arr)
localStorage.getItem('arr') // "1,2,3"
// JSON.stringify()
localStorage.setItem('arr', JSON.stringify(arr))
localStorage.getItem('arr') // "[1,2,3]"
JSON.parse(localStorage.getItem('arr')) // [1, 2, 3]

浏览器标签之前通信

让 window 监听 localStorage 的 storage,一个标签的 localStorage 发生改变时,其它标签做出相应的响应。

test0.html – 改变 localStorage。

<input type="text" id="input" />
<button onclick="setNameForStorage()">Set</button>
<script type="text/javascript">
    function setNameForStorage () {
        localStorage.name = document.querySelector('#input').value
    }
</script>

test1.html – 响应 localStorage 的改变。

<script type="text/javascript">
    window.addEventListener('storage', e => {
        console.log(e.key, e.newValue) // name 123
    })
</script>

注意

  1. localStorage 只能同域名下使用,可以搭配 postMessage 和 iframe 实现跨域通信。
  2. 低版本IE不支持 localStorage。
  3. 需在服务器环境下使用,即不能在 file:// 等非正常环境下使用。
  4. 在移动端 localStorage(H5, IOS, Android)会发生不可预知的问题。

其它

Please Stop Using Local Storage

vue-router路由懒加载

正常配置

import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/components/pages/login'
import Capture from '@/components/pages/capture'
import List from '@/components/pages/list'
import CaptureFail from '@/components/pages/capture-fail'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Login',
      component: Login
    },
    {
      path: '/capture',
      name: 'Capture',
      component: Capture
    },
    {
      path: '/list',
      name: 'List',
      component: List
    },
    {
      path: '/capture-fail',
      name: 'CaptureFail',
      component: CaptureFail
    }
  ]
})

懒加载配置

import Vue from 'vue'
import Router from 'vue-router'
// import Login from '@/components/pages/login'
// import Capture from '@/components/pages/capture'
// import List from '@/components/pages/list'
// import CaptureFail from '@/components/pages/capture-fail'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Login',
      component: resolve => require(['@/components/pages/login'], resolve) // Login
    },
    {
      path: '/capture',
      name: 'Capture',
      component: resolve => require(['@/components/pages/capture'], resolve) // Capture
    },
    {
      path: '/list',
      name: 'List',
      component: resolve => require(['@/components/pages/list'], resolve) // List
    },
    {
      path: '/capture-fail',
      name: 'CaptureFail',
      component: resolve => require(['@/components/pages/capture-fail'], resolve) // CaptureFail
    }
  ]
})

说明

npm run build后会新增路由数量相匹配的.js文件,在切到相应路由时动态加载这个文件。

参考

懒加载 · vue-router

vue-router路由懒加载(解决vue项目首次加载慢)

关于this,作用域,属性,原型链的一个小练习

function p () {
    this.name = 'x';
    var name = 'y';
    this.getName = function () {
        return name;
    }
}
// 求值
console.log(new p().getName());

getName方法里面返回name值,此时name值从作用域里面找,即var name = 'y';,返回y;
将getName方法里面的return name;改成return this.name;,这时会从this里面找属性为name的值,即'this.name = 'x';',返回x(之前一直觉得this取决于定义环境,而不取决于执行环境,后来得到一种说法是this取决于调用环境,包括方法调用、函数调用、构造器调用和apply/call调用,其实这边的调用和定义是一个意思,比如上面的this便是构造器调用)。

function p () {
//    this.name = 'x';
//    var name = 'y';
    this.getName = function () {
        return this.name;
    }
}
p.prototype.name = 'z';
// 求值
console.log(new p().getName());

若找不到属性便会在原型链上找name,即p.prototype.name = 'z';,返回z。

for…of 与 for…in 区别

一、for…of

1.定义

for…of 语句遍历可迭代对象(包括数组、Set 和 Map 结构、arguments 对象、DOM NodeList 对象、字符串等)。

2.语法

for (variable of iterable) {
    //statements
}

3.示例

<ul>
    <li>mazey</li>
    <li>luna</li>
    <li>cherrie</li>
</ul>
<script>
// 数组
let arr = ['mazey', 'luna', 'cherrie'];
for (let v of arr) {
    console.log(v);
}
// mazey luna cherrie

// 字符串
let str = 'mlc';
for (let v of str) {
    console.log(v);
}
// m l c

// 类数组对象
let obj = {
    0: 'mazey',
    1: 'luna',
    2: 'cherrie',
    length: 3
};
// 需使用Array.from转换成可迭代对象
for (let v of Array.from(obj)) {
    console.log(v);
}
// mazey luna cherrie

// Set
let s = new Set(['mazey', 'luna', 'cherrie']);
for (let v of s) {
    console.log(v);
}
// mazey luna cherrie

// Map
let m = new Map([
    ['name0', 'mazey'],
    ['name1', 'luna'],
    ['name2', 'cherrie']
]);
for (let [i, v] of m) {
    console.log(v);
}
// mazey luna cherrie

// DOM NodeList
let domList = document.querySelectorAll('li');
for (let v of domList) {
    console.log(v.innerText);
}
// mazey luna cherrie
</script>

二、for…of 与 for…in 区别

1.for…in 遍历键名,for…of 遍历键值

let arr = ['mazey', 'luna', 'cherrie'];
for (let k in arr) {
    console.log(k);
}
// 0 1 2
for (let v of arr) {
    console.log(v);
}
// mazey luna cherrie

2.for…in 会把对象上手动添加的属性和原型上的属性暴露出来

let obj = {
    0: 'mazey',
    1: 'luna',
    2: 'cherrie',
    length: 3
};
obj.name = 'objName';
for (let k in obj) {
    console.log(k);
}
// 0 1 2 length name
for (let v of Array.from(obj)) {
    console.log(v);
}
// mazey luna cherrie

三、for…of 其它优点

1.相对于数组自带的 forEach 方法,for…of 可以与 break、continue 和 return 配合使用。
2.正确识别32位 UTF-16 字符。

ES6 Promise对象then方法链式调用

then()方法的作用是Promise实例添加解决(fulfillment)和拒绝(rejection)状态的回调函数。then()方法会返回一个的Promise实例,所以then()方法后面可以继续跟另一个then()方法进行链式调用。

let p = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 'success');
});
p.then(
    res => {
        console.log(res);
        return `${res} again`;
    }
)
    .then(
        res => console.log(res)
    );
// 连续
// success
// success again

但是前一个then()方法中的回调函数中又可能返回一个Promise实例,这时候后面一个then()方法中的回调函数会等前一个Promise实例的状态发生变化才会调用。

let p = new Promise((resolve, reject) => {
    setTimeout(resolve, 1000, 'success');
});
p.then(
    res => {
        console.log(res);
        return new Promise((resolve, reject) => {
            setTimeout(resolve, 1000, 'success');
        });
    }
)
    .then(
        res => console.log(res)
    );
// 相隔1000ms
// success
// success

ES6通过WeakMap解决内存泄漏问题

一、Map

1.定义

Map对象保存键值对,类似于数据结构字典;与传统上的对象只能用字符串当键不同,Map对象可以使用任意值当键。

2.语法

new Map([iterable])

属性

  • size:返回键值对的数量。

操作方法

  • set(key, value):设置(新增/更新)键key的值为value,返回Map对象。
  • get(key):读取键key的值,没有则返回undefined。
  • has(key):判断一个Map对象中是否存在某个键值对,返回true/false。
  • delete(key):删除某个键值对,返回true/false。
  • clear():清除Map对象中所有键值对。

遍历方法

  • keys():返回键名的Iterator对象。
  • values():返回键值的Iterator对象。
  • entries():返回键值对的Iterator对象。
  • forEach((value, key, map) => {}):遍历Map对象所有键值对。

3.示例

操作方法

let m = new Map([
    ['foo', 11],
    ['bar', 22]
]);
m.set('mazey', 322)
    .set('mazey', 413);
console.log(m); // {"foo" => 11, "bar" => 22, "mazey" => 413}
console.log(m.has('mazey')); // true
m.delete('mazey');
console.log(m.has('mazey')); // false
m.clear();
console.log(m); // {}

遍历方法

let m = new Map([
    ['foo', 11],
    ['bar', 22],
    ['mazey', 413]
]);
console.log(m); // {"foo" => 11, "bar" => 22, "mazey" => 413}
console.log(m.keys()); // MapIterator {"foo", "bar", "mazey"}
console.log(m.values()); // MapIterator {11, 22, 413}
console.log(m.entries()); // MapIterator {"foo" => 11, "bar" => 22, "mazey" => 413}
m.forEach((value, key, map) => {
    console.log("键:%s,值:%s", key, value);
});
// 键:foo,值:11
// 键:bar,值:22
// 键:mazey,值:413

二、WeakMap

1.定义

WeakMap对象保存键值对,与Map不同的是其键必须是对象,因为键是弱引用,在键对象消失后自动释放内存。

2.语法

new WeakMap([iterable])

方法

  • set(key, value):设置(新增/更新)键key的值为value,返回WeakMap对象。
  • get(key):读取键key的值,没有则返回undefined。
  • has(key):判断一个WeakMap对象中是否存在某个键值对,返回true/false。
  • delete(key):删除某个键值对,返回true/false。
注意

因为WeakMap的特殊的垃圾回收机制,所以没有clear()方法。

3.示例

let obj = {
    foo: 11
};
let wm = new WeakMap();
wm.set(obj, 413322);
console.log(wm); // {{…} => 413322}
console.log(wm.has(obj)); // true

三、通过WeakMap解决内存泄漏问题

当使用Dom对象绑定事件时,Dom对象消失后若没有及时释放内存(置null),便会一直存在内存中。
使用WeakMap保存Dom对象不会出现这样的问题,因为Dom对象消失后,JS的垃圾回收机制便会自动释放其所占用的内存。

<button type="button" id="btn">按钮</button>
<script>
let wm = new WeakMap();
let btn = document.querySelector('#btn');
wm.set(btn, {count: 0});
btn.addEventListener('click', () => {
    let v = wm.get(btn);
    v.count++;
    console.log(wm.get(btn).count);
});
// 1 2 3 4 5...
</script>