Coder Social home page Coder Social logo

blog's Introduction

Hello ! I'm MiyueFE.

👨🏻‍💻 关于我

  • 2017 年毕业于 重庆邮电大学 网络工程专业
  • 2019 年正式开始从事前端开发工作
  • 我目前居住在重庆, 也希望以后能一直在重庆工作和生活

💬 联系我

Juejin Zhihu Twitter QQmail

🧠 编码状态

Stats Top Langs

🔧 技术栈与工具

Webpack TypeScript Sass Less npm html5 Nodejs Vue
Docker github actions git WebStorm MarkDown

🔓 主要开源项目

🎁 Projects ⭐ Stars 📚 Forks 🛎 Issues
Bpmn Process Designer:Vue 2 + JS 的 Bpmn 绘制工具 GitHub stars Gitee stars GitHub fork Gitee fork Issues
Bpmn Process Designer:Vue 3 + TS 的 Bpmn 绘制工具 GitHub stars Gitee stars GitHub fork Gitee fork Issues
Data visualization:Vue 3 自定义大屏可视化项目 GitHub stars Gitee stars GitHub stars Gitee fork Issues

🔓 其他开源项目

🎁 Projects ⭐ Description 💻 Website
Useful Code:Vue 组件、函数、CSS 动画、开发资源收集 包含简易钉钉流程组件、任务看板、Vue 大屏数据组件、echarts 图表集、常用数据处理函数等内容 https://miyuesc.github.io/useful-code/
Native Juejin App: 掘金非官方小程序 原生微信小程序,主要包含个人数据中心,沸点,文章,已购买小册等 -
Daily Juejin Tea Extension: 每日掘金下午茶辅助扩展程序 读取已打开的掘金文章页面,快速生成下午茶消息和表格信息 -
chibivue-zh: chibivue 中文翻译 日文仓库 chibivue:一步一步开始编写一个 vue.js 的中文翻译仓库 https://miyuesc.github.io/chibivue-zh/
auto-sync-blog: 个人掘金文章统计助手 自动抓取用户所有掘金文章与专栏,按照发布年月、分类、标签生成 vitepress 统计博客 https://miyuesc.github.io/auto-sync-blog/

image-20240117164127927 Bpmn-js 相关工具库

🎁 Projects
⭐ Description ☄ Downloads ✨ License
diagram-js-grid-bg A visual grid backgroud for diagram-js, base on diagram-js-grid.
基于 diagram-js-grid 的 SVG 网格背景,可用于diagram-js的相关项目,例如 bpmn-js、dmn-js 等。
NPM Downloads NPM License
diagram-js-context-pad An element context menu component for diagram-js/bpmn-js use, base on diagram-js/lib/features/context-pad.
一个提供给 diagram-js/bpmn-js 使用的元素上下文菜单组件,基于 diagram-js/lib/features/context-pad
NPM Downloads NPM License
diagram-js-accordion-palette A palette that supports folding and unfolding, provided for diagram-js use。Base on diagram-js/palette
一个支持折叠展开的调色板,提供给 diagram-js 使用。基于 diagram-js 本身的 Palette。
NPM Downloads NPM License
bpmn-js-i18n-zh Chinese internationalization resources for bpmn-js.
关于 bpmn-js-properties-panel 的中文支持。
NPM Downloads NPM License
bpmn-js-external-label-modeling A bpmn-js plugin used to render Label tags outside of nodes.
一个用来将Label标签渲染在节点外部的bpmn-js插件。
NPM Downloads NPM License

image-20240117163833000 闭源项目

🎁 Projects
⭐ Description 💻 Website
vue-bpmn-process-designer 基于 Vue 3 和 Typescript 的 BPMN 流程图编辑器,支持配置条件渲染、Popover 弹窗、自定义面板、多 Modeler 共存、函数式多元素插入等。 https://bpmn.miyuefe.cn

visitors

blog's People

Contributors

miyuesc avatar qijiecq avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

blog's Issues

《JavaScript高级程序设计》读书笔记(二)

summary_start
《JavaScript高级程序设计》读书笔记
summary_end

1. 函数表达式

函数定义的两种方式:函数声明,函数表达式。

函数声明的语法形式 function fn(arg0, arg1) {}

函数表达式语法形式 var fn = function(arg1, arg2) {}

函数声明存在函数声明提升:在执行代码之前会先读取函数声明,表示可以将函数声明放在调用他的语句之后。

注:使用函数表达式声明函数,在使用前必须先赋值(调用函数之前)。

sayHi();    //错误:函数还不存在 
var sayHi = function(){ 
    alert("Hi!"); 
};

1.1 递归

递归函数是在一个函数通过名字调用自身的情况下构成的。

function factorial(num) {
	if (num <= 1) {
        retrun 1;
    } else {
        retrun num * factorial(num - 1);
    }
}

这是一个经典的地归阶乘函数。但是这个函数有可能会出现下面的问题。

var newFac = factorial;
factorial = null;
alert(newFac(4)); // 报错!

因为newFac中保存了factorial()函数,将factorial变量设置为null,会导致后面在执行newFac()时会调用factorial(),但此时factorial已经不再是一个函数了。

这种情况可以用arguments.callee解决。

function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * arguments.callee(num - 1);
    }
}

使用 arguments.callee() 可以代替函数名,已确保无论怎么调用函数都不会出错。

但是在严格模式下使用脚本直接访问 arguments.callee 属性会导致错误,可以使用命名函数表达式来达成相同的结果。

var factorial = (function f(num){ 
    if (num <= 1){  
        return 1; 
    } else { 
        return num * f(num-1); 
    } 
});

1.2 闭包

闭包是指有权访问另一个函数作用域中的变量的函数。

function createComparisonFunction(propertyName) {     
    return function(object1, object2){ 
        var value1 = object1[propertyName]; // 1
        var value2 = object2[propertyName]; // 2
         
        if (value1 < value2){ 
            return -1; 
        } else if (value1 > value2){ 
            return 1; 
        } else { 
            return 0; 
        } 
    }; 
} 

这段代码中标记的这两行代码,访问了外部函数中的变量 propertyName

当函数被调用时,会创建一个执行环境(execution context)以及对应的作用域链,然后使用 arguments 和其他命名参数的值来初始化函数的活动对象(activation object,AO)。在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,直到全局执行环境。

ES历史版本特性汇总之ES6

summary_start
本文主要汇总ES2015版本发布的新特性。
summary_end

ES全称ECMAScript,ECMAScript是ECMA制定的标准化脚本语言。目前JavaScript使用的ECMAScript版本为ECMA-417。关于ECMA的最新资讯可以浏览ECMA news查看。

ECMA规范最终由TC39敲定。TC39由包括浏览器厂商在内的各方组成,他们开会推动JavaScript提案沿着一条严格的发展道路前进。
从提案到入选ECMA规范主要有以下几个阶段:

  • strawman——最初想法的提交。
  • proposal(提案)——由TC39至少一名成员倡导的正式提案文件,该文件包括API事例。
  • draft(草案)——功能规范的初始版本,该版本包含功能规范的两个实验实现。
  • candidate(候选)——提案规范通过审查并从厂商那里收集反馈
  • finished(完成)——提案准备加入ECMAScript,但是到浏览器或者Nodejs中可能需要更长的时间。

ES6新特性(2015)

ES6的特性比较多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化。两个发布版本之间时间跨度很大,所以ES6中的特性比较多。 在这里列举几个常用的:

  • 模块化
  • 箭头函数
  • 函数参数默认值
  • 模板字符串
  • 解构赋值
  • 延展操作符
  • 对象属性简写
  • Promise
  • Let与Const

1. 类(class)

对熟悉Java,object-c等纯面向对象语言的开发者来说,都会对class有一种特殊的情怀。ES6 引入了class(类),让JavaScript的面向对象编程变得更加简单和易于理解。

  class Animal {
    // 构造函数,实例化的时候将会被调用,如果不指定,那么会有一个不带参数的默认构造函数.
    constructor(name,color) {
      this.name = name;
      this.color = color;
    }
    // toString 是原型对象上的属性
    toString() {
      console.log('name:' + this.name + ',color:' + this.color);

    }
  }

 var animal = new Animal('dog','white');//实例化Animal
 animal.toString();

 console.log(animal.hasOwnProperty('name')); //true
 console.log(animal.hasOwnProperty('toString')); // false
 console.log(animal.__proto__.hasOwnProperty('toString')); // true

 class Cat extends Animal {
  constructor(action) {
    // 子类必须要在constructor中指定super 函数,否则在新建实例的时候会报错.
    // 如果没有置顶consructor,默认带super函数的constructor将会被添加、
    super('cat','white');
    this.action = action;
  }
  toString() {
    console.log(super.toString());
  }
 }

 var cat = new Cat('catch')
 cat.toString();

 // 实例cat 是 Cat 和 Animal 的实例,和Es5完全一致。
 console.log(cat instanceof Cat); // true
 console.log(cat instanceof Animal); // true

2. 模块化(Module)

ES5不支持原生的模块化,在ES6中模块作为重要的组成部分被添加进来。模块的功能主要由 export 和 import 组成。每一个模块都有自己单独的作用域,模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口,通过import来引用其它模块提供的接口。同时还为模块创造了命名空间,防止函数的命名冲突。

导出(export)

ES6允许在一个模块中使用export来导出多个变量或函数。

导出变量

export var name = 'Rainbow'

心得:ES6不仅支持变量的导出,也支持常量的导出。

 export const sqrt = Math.sqrt;//导出常量

ES6将一个文件视为一个模块,上面的模块通过 export 向外输出了一个变量。一个模块也可以同时往外面输出多个变量。

 //test.js
 var name = 'Rainbow';
 var age = '24';
 export {name, age};

导出函数

// myModule.js
export function myModule(someArg) {
  return someArg;
}

导入(import)

定义好模块的输出以后就可以在另外一个模块通过import引用。

import {myModule} from 'myModule';// main.js
import {name,age} from 'test';// test.js

心得:一条import 语句可以同时导入默认函数和其它变量。

import defaultMethod, { otherMethod } from 'xxx.js';

3. 箭头(Arrow)函数

这是ES6中最令人激动的特性之一。=>不只是关键字function的简写,它还带来了其它好处。箭头函数与包围它的代码共享同一个this,能帮你很好的解决this的指向问题。有经验的JavaScript开发者都熟悉诸如var self = this;或var that = this这种引用外围this的模式。但借助=>,就不需要这种模式了。

箭头函数的结构

箭头函数的箭头=>之前是一个空括号、单个的参数名、或用括号括起的多个参数名,而箭头之后可以是一个表达式(作为函数的返回值),或者是用花括号括起的函数体(需要自行通过return来返回值,否则返回的是undefined)。

// 箭头函数的例子
()=>1
v=>v+1
(a,b)=>a+b
()=>{
    alert("foo");
}
e=>{
    if (e == 0){
        return 0;
    }
    return 1000/e;
}

心得:不论是箭头函数还是bind,每次被执行都返回的是一个新的函数引用,因此如果你还需要函数的引用去做一些别的事情(譬如卸载监听器),那么你必须自己保存这个引用。

卸载监听器时的陷阱

错误的做法

class PauseMenu extends React.Component{
    componentWillMount(){
        AppStateIOS.addEventListener('change', this.onAppPaused.bind(this));
    }
    componentWillUnmount(){
        AppStateIOS.removeEventListener('change', this.onAppPaused.bind(this));
    }
    onAppPaused(event){
    }
}

正确的做法

class PauseMenu extends React.Component{
    constructor(props){
        super(props);
        this._onAppPaused = this.onAppPaused.bind(this);
    }
    componentWillMount(){
        AppStateIOS.addEventListener('change', this._onAppPaused);
    }
    componentWillUnmount(){
        AppStateIOS.removeEventListener('change', this._onAppPaused);
    }
    onAppPaused(event){
    }
}

除上述的做法外,我们还可以这样做:

class PauseMenu extends React.Component{
    componentWillMount(){
        AppStateIOS.addEventListener('change', this.onAppPaused);
    }
    componentWillUnmount(){
        AppStateIOS.removeEventListener('change', this.onAppPaused);
    }
    onAppPaused = (event) => {
        //把函数直接作为一个arrow function的属性来定义,初始化的时候就绑定好了this指针
    }
}

需要注意的是:不论是bind还是箭头函数,每次被执行都返回的是一个新的函数引用,因此如果你还需要函数的引用去做一些别的事情(譬如卸载监听器),那么你必须自己保存这个引用。

4. 函数参数默认值

ES6支持在定义函数的时候为其设置默认值:

function foo(height = 50, color = 'red')
{
    // ...
}

不使用默认值:

function foo(height, color)
{
    var height = height || 50;
    var color = color || 'red';
    //...
}

这样写一般没问题,但当参数的布尔值为false时,就会有问题了。比如,我们这样调用foo函数:

foo(0, "")

因为0的布尔值为false,这样height的取值将是50。同理color的取值为‘red’。
所以说,函数参数默认值不仅能是代码变得更加简洁而且能规避一些问题。

5. 模板字符串

ES6支持模板字符串,使得字符串的拼接更加的简洁、直观。

// 不使用模板字符串:
var name = 'Your name is ' + first + ' ' + last + '.'

// 使用模板字符串:
var name = `Your name is ${first} ${last}.`

在ES6中通过" ${} "就可以完成字符串的拼接,只需要将变量放在大括号之中。

6. 解构赋值

解构赋值语法是JavaScript的一种表达式,可以方便的从数组或者对象中快速提取值赋给定义的变量。

获取数组中的值

从数组中获取值并赋值到变量中,变量的顺序与数组中对象顺序对应。

var foo = ["one", "two", "three", "four"];

var [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"

//如果你要忽略某些值,你可以按照下面的写法获取你想要的值
var [first, , , last] = foo;
console.log(first); // "one"
console.log(last); // "four"

//你也可以这样写
var a, b; //先声明变量

[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2

如果没有从数组中的获取到值,你可以为变量设置一个默认值。

var a, b;

[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7

通过解构赋值可以方便的交换两个变量的值。

var a = 1;
var b = 3;

[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1

获取对象中的值

const student = {
  name:'Ming',
  age:'18',
  city:'Shanghai'  
};

const {name,age,city} = student;
console.log(name); // "Ming"
console.log(age); // "18"
console.log(city); // "Shanghai"

7. 延展操作符(Spread operator)

延展操作符...可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造对象时, 将对象表达式按key-value的方式展开。

语法

// 函数调用:
myFunction(...iterableObj);

// 数组构造或字符串:
[...iterableObj, '4', ...'hello', 6];

// 构造对象时,进行克隆或者属性拷贝(ECMAScript 2018规范新增特性):
let objClone = { ...obj };

应用场景

  1. 在函数调用时使用延展操作符
function sum(x, y, z) {
  return x + y + z;
}
const numbers = [1, 2, 3];

//不使用延展操作符
console.log(sum.apply(null, numbers));

//使用延展操作符
console.log(sum(...numbers));// 6
  1. 构造数组

没有展开语法的时候,只能组合使用 push,splice,concat 等方法,来将已有数组元素变成新数组的一部分。有了展开语法, 构造新数组会变得更简单、更优雅

const stuendts = ['Jine','Tom']; 
const persons = ['Tony',... stuendts,'Aaron','Anna'];
conslog.log(persions)// ["Tony", "Jine", "Tom", "Aaron", "Anna"]

和参数列表的展开类似, ... 在构造字数组时, 可以在任意位置多次使用。

  1. 数组拷贝
var arr = [1, 2, 3];
var arr2 = [...arr]; // 等同于 arr.slice()
arr2.push(4); 
console.log(arr2)//[1, 2, 3, 4]
  1. 连接多个数组
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
var arr3 = [...arr1, ...arr2];// 将 arr2 中所有元素附加到 arr1 后面并返回
//等同于
var arr4 = arr1.concat(arr2);

在ECMAScript 2018中延展操作符增加了对对象的支持

var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };

var clonedObj = { ...obj1 };
// 克隆后的对象: { foo: "bar", x: 42 }

var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象: { foo: "baz", x: 42, y: 13 }

在React中的应用

通常我们在封装一个组件时,会对外公开一些 props 用于实现功能。大部分情况下在外部使用都应显示的传递 props 。但是当传递大量的props时,会非常繁琐,这时我们可以使用 ...(延展操作符,用于取出参数对象的所有可遍历属性) 来进行传递。

作者:上沅兮

链接:https://juejin.im/post/5ca2e1935188254416288eb2

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Bpmn.js (一)

summary_start
Bpmn.js 文档介绍
summary_end

Bpmn.js

一. 引入Bpmn.js并初始化建模器

/* 基于vue2.x, 省略了template模板与部分data */

import BpmnModeler from "bpmn-js/lib/Modeler"

export default {
    methods: {
        initModeler() {
            this.bpmnModeler = new BpmnModeler({
                container: document.getElementById("bpmn-container")
            })
        }
    },
    mounted() {
        this.initModeler();
    }
}

进入到源文件Modeler.js,可以找到创建Modeler建模器的时候需的参数。

this.bpmnModeler = new BpmnModeler(options: Options);

interface Options {
	container: DomElement; // 渲染容器
	width:string | number;// 查看器宽度
	height: string | number; // 查看器高度
	moddleExtensions: object;// 需要用的扩展包
	modules:<didi.Module>[]; // 自定义且需要覆盖默认扩展包的模块列表
	additionalModules: <didi.Module>[]; // 自定义且与默认扩展包一起使用的模块列表
}

初始化完成之后,在控制台打印this.bpmnModeler,可以发现BpmnModeler类继承了多个基础类。

Modeler
	-> BaseModeler
		-> BaseViewer
			-> Diagram
				-> Object

Bpmn.js提供的默认扩展包名称,可以在this.bpmnModeler.proto._modules内找到,一共开放了32个扩展包。扩展包名称可以在this.bpmnModeler.injector._providers内,包名即键名。

需要调用这些扩展包时,可以使用如下方式:

const xxxModule = this.bpmnModeler.get("xxx"); // xxx代表扩展包名称

Modeler实例化之后可直接调用的方法:

/**
 * 返回name对应的模块实例
 * @param { string } name 模块名
 * @param { boolean } strict 启用严格模式。false:缺少的模块解析为null返回;true:抛出异常
 */
this.bpmnModeler.get(name, strict);

// 创建空白流程图
// 内部调用了importXML方法,读取内部的默认xml字符串
this.bpmnModeler.createDiagram();

// 将图形dom挂载到目标节点
this.bpmnModeler.attachTo(parentNode: DomElement);

// 清空
this.bpmnModeler.clear()

// 销毁
this.bpmnModeler.destroy()

// 脱离dom
this.bpmnModeler.detach()

// 获取流程定义
this.bpmnModeler.getDefinitions()

// 获取扩展功能模块列表
this.bpmnModeler.getModules()

/**
 * 导入解析的定义并呈现BPMN 2.0图。完成后,查看器将结果报告回给提供的回调函数(错误,警告)
 * @param { ModdleElement<Definitions> } definitions 模块名
 * @param { ModdleElement<BPMNDiagram>|string } [bpmnDiagram] 要呈现的BPMN图或图的ID(如果未提供,将呈现第一个)
 */
this.bpmnModeler.importDefinitions(definitions, bpmnDiagram)

/**
 * 导入xml(字符串形式),返回导入结果
 * 后续会取消传入回调函数的方式
 * 推荐使用async/await或者链式调用
 * @param { string } xml 流程图xml字符串
 * @param { Promise } callback 回调函数,出错时返回{ warnings,err }
 */
this.bpmnModeler.importXML(xml, callback)

// 注销事件监听器
this.bpmnModeler.off(eventName, callback)

// 注册事件监听,同名将删除以前的监听器,privorty可不传,程序会自动替换回调函数
this.bpmnModeler.on(eventName, priority, callback, target)

// em。。。不了解
this.bpmnModeler.open()

// 保存为svg文件,与importXML方法一样,后续会取消传入callback的方式
this.bpmnModeler.saveSVG(callback)

// 保存为xml文件,与importXML方法一样,后续会取消传入callback的方式
this.bpmnModeler.saveXML(callback)
	

二. 基础功能

使用过程中,常用的组件主要包含:palette 左侧元素栏、 contentPad 元素操作块、 propertiesPanel右侧元素属性编辑栏

1. palette 与 contentPad

这两个组件在实例化建模器的时候已经渲染到了页面上,只需要引入对应的样式文件即可。

import "bpmn-js/dist/assets/diagram-js.css";
import "bpmn-js/dist/assets/bpmn-font/css/bpmn.css";
import "bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css";

2. propertiesPanel

使用这个组件需要在实例化建模器时修改配置项,并引入对应样式文件

import "bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css"; // 右边工具栏样式

import propertiesPanelModule from "bpmn-js-properties-panel";
import propertiesProviderModule from "bpmn-js-properties-panel/lib/provider/camunda";

export default {
    methods: {
        initModeler() {
            this.bpmnModeler = new BpmnModeler({
                container: document.getElementById("bpmn-container")
                propertiesPanel: {
                  parent: "#attrs-panel"
                },	
                additionalModules:[ propertiesPanelModule, propertiesProviderModule ]
            })
        }
    },
    mounted() {
        this.initModeler();
    }
}

3. 汉化

在汉化之前,可以在github或者码云上找到很多大佬的翻译文件,将翻译文件下载下载保存到本地。

// 1. 创建翻译文件 zh.js(命名随意),导出英文关键字对应的中文翻译
export default {
    "Append EndEvent": "追加结束事件",
    "Append Gateway": "追加网关",
    "Append Task": "追加任务",
    "Append Intermediate/Boundary Event": "追加中间抛出事件/边界事件",
    ...
}
    
// 2. 建立翻译模块方法customTranslate.js
import translations from "./zh";

export default function customTranslate(template, replacements) {
  replacements = replacements || {};

  // Translate
  template = translations[template] || template;

  // Replace
  return template.replace(/{([^}]+)}/g, function(_, key) {
    let str = replacements[key];
    if (
      translations[replacements[key]] !== null &&
      translations[replacements[key]] !== "undefined"
    ) {
      // eslint-disable-next-line no-mixed-spaces-and-tabs
      str = translations[replacements[key]];
      // eslint-disable-next-line no-mixed-spaces-and-tabs
    }
    return str || "{" + key + "}";
  });
}

// 3. 在实例化建模器时以自定义模块的方式传入参数
import customTranslate from "./pugins/customTranslate";

initModeler() {
    this.bpmnModeler = new BpmnModeler({
        container: document.getElementById("bpmn-container")
        additionalModules:[
        	{ translate: ["value", customTranslate] }
        ]
    })
}

翻译文件来自码云,但是忘记了作者的信息了,如果作者发现请您联系我更改或者删除

4. 其他功能(非自定义的功能模块配置项)

添加键盘快捷键:

this.bpmnModeler = new BpmnModeler({
    container: document.getElementById("bpmn-container")
    keyboard: {
    	bindTo: document // 或者window,注意与外部表单的键盘监听事件是否冲突
    },
});

持续更新。。。

三. 事件

Bpmn.js 提供了EventBus事件总线模块来管理监听事件,并预设了244个事件。

下面的元素对象指包含element元素的对象,其他属性不定(部分事件返回的对象也不包含element)。

“ - ” 短横线表示暂时没有找到何时触发该事件

以下事件均可使用this.bpmnModeler.on(eventName, callback)或者eventBus.on(eventName, callback)的形式注册。

// 通过事件总线发出的事件
interface InternalEvent {
    type: string; // 发生的事件名称,但是很快会被置为undefined
    element: Shape | Connection;
    elements: Element[];
    shape: Shape;
    context: object; // 有点复杂,有兴趣的朋友可以研究
    gfx?: SVGElement;
    svg?: SVGElement;
    viewport?: SVGElement;
    viewbox?: Viewbox;
}

interface Element {
    element: Shape | Connection;
    gfx?: SVGElement;
}

interface Elements {
    elements: Array<Shape | Connection>
}

interface Canvas {
    svg?: SVGElement;
    viewport?: SVGElement;
}

interface Viewbox {
	viewbox: {
        height: number;
        width: number;
        x: number;
        y: number;
        inter: object; // 包含x,y,width,height的一个对象
        outer: object; // 包含x,y,width,height的一个对象
        scale: number; // 当前缩放比例(小数)
	}
}
序号 事件名 说明 callback参数
0 "diagram.destroy" 流程编辑器销毁 event:InternalEvent
1 "render.shape" -
2 "render.connection" -
3 "render.getShapePath" -
4 "render.getConnectionPath" -
5 "diagram.init" 指示画布已准备好在其上进行绘制
6 "shape.added" 已更新到xml内,触发渲染方法,返回值为插入的新元素 event:InternalEvent, element: Element
7 "connection.added" 已更新到xml内,触发渲染方法,返回值为插入的新元素 event:InternalEvent, element: Element
8 "shape.removed" 形状移除完成,返回值为被移除元素 event:InternalEvent, element: Element
9 "connection.removed" 连线移除完成,返回值为被移除元素
10 "elements.changed" 元素发生改变并更改完成 event: InternalEvent, element: Elements
11 "diagram.clear" 流程编辑器元素及画布已清空 event:InternalEvent
12 "canvas.destroy" 画布销毁 event:InternalEvent
13 "canvas.init" 画布初始化完成
14 "shape.changed" 形状属性更新,返回当前元素 event:InternalEvent, element: Element
15 "connection.changed" 连线属性更新,返回当前元素 event:InternalEvent, element: Element
16 "interactionEvents.createHit"
17 "interactionEvents.updateHit"
18 "shape.remove" 形状被选中移除,返回被移除的元素对象 event:InternalEvent, element: Element
19 "connection.remove" 连线被选中移除 event:InternalEvent, element: Element
20 "element.hover" 鼠标移动到元素上,返回鼠标位置处元素对象 event:InternalEvent, element: Element
21 "element.out" 鼠标移出元素,返回鼠标最近移入的元素对象 event:InternalEvent, element: Element
22 "selection.changed" 选中元素变化时,返回新选中的元素对象 event:InternalEvent, element: Element
23 "create.end" 从palette中新建的元素创建完成(不清楚为什么有两个相同的参数) event:InternalEvent, event:InternalEvent
24 "connect.end" 从palette中或者从选中节点中新建的连线元素创建完成(不清楚为什么有两个相同的参数) event:InternalEvent, event:InternalEvent
25 "shape.move.end" 形状元素移动结束后 event:InternalEvent, element: Element
26 "element.click" 元素单击事件 event:InternalEvent, element: Element
27 "canvas.viewbox.changing" 视图缩放过程中 event:InternalEvent
28 "canvas.viewbox.changed" 视图缩放完成 event:InternalEvent, viewbox: Viewbox
29 "element.changed" 元素发生改变时触发,返回发生改变的元素 event:InternalEvent, element: Element
30 "element.marker.update"
31 "attach"
32 "detach"
33 "editorActions.init"
34 "keyboard.keydown" 键盘按键按下
35 "element.mousedown" 鼠标在元素上按下时触发 event:InternalEvent, element: Element
36 "commandStack.connection.start.canExecute"
37 "commandStack.connection.create.canExecute"
38 "commandStack.connection.reconnect.canExecute"
39 "commandStack.connection.updateWaypoints.canExecute"
40 "commandStack.shape.resize.canExecute"
41 "commandStack.elements.create.canExecute"
42 "commandStack.elements.move.canExecute"
43 "commandStack.shape.create.canExecute"
44 "commandStack.shape.attach.canExecute"
45 "commandStack.element.copy.canExecute"
46 "shape.move.start" 形状开始移动 event:InternalEvent, element: Element
47 "shape.move.move"
48 "elements.delete" 元素被删除
49 "tool-manager.update"
50 "i18n.changed"
51 "drag.move"
52 "contextPad.create"
53 "palette.create"
54 "autoPlace.end"
55 "autoPlace"
56 "drag.start"
57 "drag.init"
58 "drag.cleanup"
59 "commandStack.shape.create.postExecuted"
60 "commandStack.elements.move.postExecuted"
61 "commandStack.shape.toggleCollapse.postExecuted"
62 "commandStack.shape.resize.postExecuted"
63 "commandStack.element.autoResize.canExecute"
64 "bendpoint.move.hover"
65 "bendpoint.move.out"
66 "bendpoint.move.cleanup"
67 "bendpoint.move.end"
68 "connectionSegment.move.start"
69 "connectionSegment.move.move"
70 "connectionSegment.move.hover"
71 "connectionSegment.move.out"
72 "connectionSegment.move.cleanup"
73 "connectionSegment.move.cancel"
74 "connectionSegment.move.end"
75 "element.mousemove"
76 "element.updateId"
77 "bendpoint.move.move"
78 "bendpoint.move.start"
79 "bendpoint.move.cancel"
80 "connect.move"
81 "connect.hover"
82 "connect.out"
83 "connect.cleanup"
84 "create.move"
85 "create.hover"
86 "create.out"
87 "create.cleanup"
88 "create.init"
89 "copyPaste.copyElement"
90 "copyPaste.pasteElements"
91 "moddleCopy.canCopyProperties"
92 "moddleCopy.canCopyProperty"
93 "moddleCopy.canSetCopiedProperty"
94 "copyPaste.pasteElement"
95 "popupMenu.getProviders.bpmn-replace"
96 "contextPad.getProviders"
97 "resize.move"
98 "resize.end"
99 "commandStack.shape.resize.preExecute"
100 "spaceTool.move"
101 "spaceTool.end"
102 "create.start"
103 "commandStack.connection.create.postExecuted"
104 "commandStack.connection.layout.postExecuted"
105 "shape.move.init"
106 "resize.start"
107 "resize.cleanup"
108 "directEditing.activate"
109 "directEditing.resize"
110 "directEditing.complete"
111 "directEditing.cancel"
112 "commandStack.connection.updateWaypoints.postExecuted"
113 "commandStack.label.create.postExecuted"
114 "commandStack.elements.create.postExecuted"
115 "commandStack.shape.append.preExecute"
116 "commandStack.shape.move.postExecute"
117 "commandStack.elements.move.preExecute"
118 "commandStack.connection.create.postExecute"
119 "commandStack.connection.reconnect.postExecute"
120 "commandStack.shape.create.executed"
121 "commandStack.shape.create.reverted"
122 "commandStack.shape.create.preExecute"
123 "shape.move.hover"
124 "global-connect.hover"
125 "global-connect.out"
126 "global-connect.end"
127 "global-connect.cleanup"
128 "connect.start"
129 "commandStack.shape.create.execute"
130 "commandStack.shape.create.revert"
131 "commandStack.shape.create.postExecute"
132 "commandStack.elements.create.preExecute"
133 "commandStack.elements.create.revert"
134 "commandStack.elements.create.postExecute"
135 "commandStack.connection.layout.executed"
136 "commandStack.connection.create.executed"
137 "commandStack.connection.layout.reverted"
138 "commandStack.shape.move.executed"
139 "commandStack.shape.delete.executed"
140 "commandStack.connection.move.executed"
141 "commandStack.connection.delete.executed"
142 "commandStack.shape.move.reverted"
143 "commandStack.shape.delete.reverted"
144 "commandStack.connection.create.reverted"
145 "commandStack.connection.move.reverted"
146 "commandStack.connection.delete.reverted"
147 "commandStack.canvas.updateRoot.executed"
148 "commandStack.canvas.updateRoot.reverted"
149 "commandStack.shape.resize.executed"
150 "commandStack.shape.resize.reverted"
151 "commandStack.connection.reconnect.executed"
152 "commandStack.connection.reconnect.reverted"
153 "commandStack.connection.updateWaypoints.executed"
154 "commandStack.connection.updateWaypoints.reverted"
155 "commandStack.element.updateAttachment.executed"
156 "commandStack.element.updateAttachment.reverted"
157 "commandStack.shape.delete.postExecute"
158 "commandStack.canvas.updateRoot.postExecute"
159 "spaceTool.selection.init"
160 "spaceTool.selection.ended"
161 "spaceTool.selection.canceled"
162 "spaceTool.ended"
163 "spaceTool.canceled"
164 "spaceTool.selection.end"
165 "commandStack.shape.delete.postExecuted"
166 "commandStack.connection.create.preExecuted"
167 "commandStack.shape.replace.preExecuted"
168 "bpmnElement.added"
169 "commandStack.element.updateProperties.postExecute"
170 "commandStack.label.create.postExecute"
171 "commandStack.connection.layout.postExecute"
172 "commandStack.connection.updateWaypoints.postExecute"
173 "commandStack.shape.replace.postExecute"
174 "commandStack.shape.resize.postExecute"
175 "shape.move.rejected"
176 "create.rejected"
177 "elements.paste.rejected"
178 "commandStack.shape.delete.preExecute"
179 "commandStack.connection.reconnect.preExecute"
180 "commandStack.element.updateProperties.postExecuted"
181 "commandStack.shape.replace.postExecuted"
182 "commandStack.shape.toggleCollapse.executed"
183 "commandStack.shape.toggleCollapse.reverted"
184 "spaceTool.getMinDimensions"
185 "commandStack.connection.delete.preExecute"
186 "commandStack.canvas.updateRoot.preExecute"
187 "commandStack.spaceTool.preExecute"
188 "commandStack.lane.add.preExecute"
189 "commandStack.lane.resize.preExecute"
190 "commandStack.lane.split.preExecute"
191 "commandStack.elements.delete.preExecute"
192 "commandStack.shape.move.preExecute"
193 "commandStack.spaceTool.postExecuted"
194 "commandStack.lane.add.postExecuted"
195 "commandStack.lane.resize.postExecuted"
196 "commandStack.lane.split.postExecuted"
197 "commandStack.elements.delete.postExecuted"
198 "commandStack.shape.move.postExecuted"
199 "saveXML.start"
200 "commandStack.connection.create.preExecute"
201 "commandStack.connection.move.preExecute"
202 "shape.move.out"
203 "shape.move.cleanup"
204 "commandStack.elements.move.preExecuted"
205 "commandStack.shape.delete.execute"
206 "commandStack.shape.delete.revert"
207 "spaceTool.selection.start"
208 "spaceTool.selection.move"
209 "spaceTool.selection.cleanup"
210 "spaceTool.cleanup"
211 "lasso.selection.init"
212 "lasso.selection.ended"
213 "lasso.selection.canceled"
214 "lasso.ended"
215 "lasso.canceled"
216 "lasso.selection.end"
217 "lasso.end"
218 "lasso.start"
219 "lasso.move"
220 "lasso.cleanup"
221 "hand.init"
222 "hand.ended"
223 "hand.canceled"
224 "hand.move.ended"
225 "hand.move.canceled"
226 "hand.end"
227 "hand.move.move"
228 "hand.move.end"
229 "global-connect.init"
230 "global-connect.ended"
231 "global-connect.canceled"
232 "global-connect.drag.ended"
233 "global-connect.drag.canceled"
234 "palette.getProviders"
235 "propertiesPanel.isEntryVisible"
236 "propertiesPanel.isPropertyEditable"
237 "root.added"
238 "propertiesPanel.changed"
239 "propertiesPanel.resized"
240 "elementTemplates.changed"
241 "canvas.resized"
242 "import.parse.complete" 读取模型(xml)完成
243 "commandStack.changed" 发生任意可撤销/恢复操作时触发,可用来实时更新xml

四. Moddles

1. ElementFactory Diagram元素工厂

用于创建各种Diagram(djs.model)元素,并赋予各种属性。

使用方式:

const ElementFactory = this.bpmnModeler.get("ElementFactory")

方法与返回值:

/**
 * 根据传入参数创建新的元素
 * type:"root" | "label" | "connection" | "shape"
 * attrs: object
 */
ElementFactory.create(type, attrs);

衍生方法:

根据create方法传入的不同type,衍生了四种创建图形元素的方法。

ElementFactory.createRoot(attrs);
ElementFactory.createLabel(attrs);
ElementFactory.createConnection(attrs);
ElementFactory.createShape(attrs);

2. ElementRegistry 图形注册表

用于追踪所有元素。注入了EventBus事件总线。

使用方式:

const ElementRegistry = this.bpmnModeler.get("ElementRegistry")

方法与返回值:

/**
 * 插入新的注册表
 * @param element:Shape | Connection
 * @param gfx: SVGElement
 * @param secondaryGfx?: SVGElement
 */
ElementRegistry.add(element, gfx, secondaryGfx);

// 移除元素
ElementRegistry.remove(element)

// 更新元素模型的id,同时触发事件'element.updateId'
ElementRegistry.updateId(element, newId)

// 获取对应id的元素模型
ElementRegistry.get(id)

// 根据传入的过滤方法返回符合条件的元素模型数组
ElementRegistry.filter(fn(element, gfx))

// 根据传入的过滤方法返回符合条件的第一个元素模型
ElementRegistry.find(fn(element, gfx))

// 返回所有已渲染的元素模型
ElementRegistry.getAll()

// 遍历所有已渲染元素模型,执行传入方法
ElementRegistry.forEach(fn(element, gfx))

/**
 * 返回对应的SVGElement
 * @param filter:string | Model id或者元素模型
 * @param secondary: boolean =false 是否返回辅助连接的元素
 */
ElementRegistry.getGraphics()

3. GraphicsFactory 图形元素工厂

用于创建可显示的图形元素。注入了EventBus事件总线与ElementRegistry注册表。

该类型几乎不直接使用

使用方式:

const GraphicsFactory = this.bpmnModeler.get("GraphicsFactory")

方法与返回值:

/**
 * 返回创建后的SVGElement
 * @param type:"shape" | "connection" 元素类型
 * @param element: SVGElement
 * @param parentIndex: number 位置
 */
GraphicsFactory.create(type, element, parentIndex)

// 绘制节点,触发"render.shape"
GraphicsFactory.drawShape(visual,element)

// 获取元素的尺寸等,触发"render.getShapePath"
GraphicsFactory.getShapePath(element)

// 绘制连线,触发"render.connection"
GraphicsFactory.drawConnection(visual,element)

// 获取连线的尺寸等,触发"render.getConnectionPath"
GraphicsFactory.getConnectionPath(waypoints)

// 更新元素显示效果(element.type只允许"shape"或者"connaction")
GraphicsFactory.update(element)

GraphicsFactory.remove(element)

4. Canvas 画布

核心模块之一,处理所有的元素绘制与显示。注入了"canvas.config", "EventBus", "GraphicsFactory", "ElementRegistry"。

使用方式:

const Canvas = this.bpmnModeler.get("Canvas")

内部方法:

/**
 * 画布初始化,根据config配置为svg元素创建一个div class="djs-container"的父容器。并挂载到制定的dom节点上
 * 并在这个div节点下创建一个svg元素,挂载后面所有节点
 * 之后触发"diagram.init"与"canvas.init"
 * 同时注册'shape.added', 'connection.added', 'shape.removed', 'connection.removed', 'elements.changed', 'diagram.destroy', 'diagram.clear'
 * config: object 默认继承实例化BpmnModeler的配置
 */
Canvas._init(config)

/**
 * 画布销毁
 * 触发"canvas.destroy"
 * 删除Canvas内部属性,并移除初始化时声称的dom节点
 */
Canvas._destroy()

/**
 * 按类型创建新的元素对象, 同时触发对应的"[shape | connection].add"和"[shape | connection].added"事件,返回创建的元素对象(衍生的addShape,addConnection方法)
 * @param type: "shape" | "connection" 元素类型
 * @param element: object | djs.model
 * @param parent?: object | djs.model 父元素,默认为根元素对象
 * @param parentIndex?: number 
 * @return element: object | djs.model
 */
Canvas._addElement(type, element, parent, parentIndex)

/**
 * 移除对应的元素, 同时触发对应的"[shape | connection].remove"和"[shape | connection].removed"事件,返回被删除的元素对象(衍生的addShape,addConnection方法)
 * @param type: "shape" | "connection" 元素类型
 * @param element: object | djs.model
 * @param parent?: object | djs.model 父元素,默认为根元素对象
 * @param parentIndex?: number 
 * @return element: object | djs.model
 */
Canvas._removeElement(element, type)

方法与返回值:

// 返回最上层的SVGElement
Canvas.getDefaultLayer()

// 返回用于在其上绘制元素或注释的图层(这个不怎么理解。。。)
Canvas.getLayer(name, index)

// 返回包含画布的Dom节点
Canvas.getContainer()

/**
 * 将标记更新包element上(基本上是css类),同时触发"element.marker.update", 无返回值
 * @param element:Shape | Connaction | string(元素id)
 * @param marker: string
 */
canvas.addMarker(element, marker)


/**
 * 移除元素上的标记,同时触发"element.marker.update", 无返回值
 * @param element:Shape | Connaction | string(元素id)
 * @param marker: string
 */
canvas.removeMarker(element, marker)

/**
 * 检测元素是否具有某个标记
 * @param element:Shape | Connaction | string(元素id)
 * @param marker: string
 * @return status: boolean
 */
Canvas.hasMarker(element, marker)

/**
 * 切换元素的标记状态,存在即remove,不存在则add
 * @param element:Shape | Connaction | string(元素id)
 * @param marker: string
 */
Canvas.toggleMarker(element, marker)

/**
 * 返回画布的根元素
 * @return element: Object|djs.model
 */
Canvas.getRootElement()

/**
 * 设置新的画布的根元素,返回新的根元素
 * @param element:object | djs.model
 * @param override: boolean 是否要覆盖以前的根元素
 * @return element: Object|djs.model
 */
Canvas.setRootElement(element, override)

/**
 * 添加新的节点(形状)图形元素
 * @param shape: object | djs.model 插入的元素model或者属性配置对象
 * @param parent?: djs.model 父元素,默认根节点(画布下第一级SVG | Root)
 * @param parentIndex?: number 
 * @return Element: djs.model
 */
Canvas.addShape(shape, parent, parentIndex)

/**
 * 添加新的连线元素
 * @param Connaction: object | djs.model 插入的元素model或者属性配置对象
 * @param parent?: djs.model 父元素,默认根节点(画布下第一级SVG | Root)
 * @param parentIndex?: number 
 * @return Element: djs.model
 */
Canvas.addConnection(Connaction, parent, parentIndex)

/**
 * 移除节点元素, 返回被移除的节点
 * @param shape: string | djs.model 节点id或者model
 * @return element: djs.model
 */
Canvas.removeShape(shape)

/**
 * 移除连线元素, 返回被移除的对象
 * @param shape: string | djs.model 节点id或者model
 * @return element: djs.model
 */
Canvas.removeConnaction(connection)

/**
 * 查询图形元素的SVGElement,即 ElementRegistry.getGraphics()方法。
 * @param shape: string | djs.model 节点id或者model
 * @param secondary: boolean 
 * @return element: SVGElement
 */
Canvas.getGraphics(element, secondary)

/**
 * 获取或者设置新的画布的视图框。该方法的getter可能会返回一个缓存中的viewbox(如果当前时刻视图正在发生改变)
 * 如果要强制重新计算,请先执行Canvas.viewbox(false)
 * @param box: Box 新的视图框配置
 * @param box.x: number = 0 视图框可见的画布区域在x轴坐标(原点左上角)
 * @param box.y: number = 0 视图框可见的画布区域在y轴坐标(原点左上角)
 * @param box.width: number 视图框可见宽度
 * @param box.height: number 视图框可见高度
 * @return box 当前视图区域
 */
Canvas.viewbox(box)

/**
 * 具有滚动条时调整滚动位置
 * @param delta:Delta 新的滚动位置
 * @param delta.dx:number x轴方向的位移大小
 * @param delta.dy:number y轴方向的位移大小
 * @return
 */
Canvas.scroll(delta)

/**
 * 设置新的缩放比例或者中心位置,返回当前的缩放比例
 * @param newScale?: number 新的缩放比例
 * @param center?: Point | "auto" | null
 * @param center.x: number
 * @param center.y: number
 * @return scale: number
 */
Canvas.zoom(newScale, center)

// 主动触发"canvas.resized"事件
Canvas.resized()

5. EventBus 事件总线

核心模块之一,通用事件总线, 该组件用于跨图实例进行通信。 图的其他部分可以使用它来侦听和广播事件。

使用方式:

const EventBus = this.bpmnModeler.get("eventBus");

核心方法:

/**
 * 注册事件监听器
 * @param events: string | string[] 事件名称(s)
 * @param priority?: number 优先级,默认1000
 * @param callback: Function 回调函数
 * @param that?: object 上下文中的this,会将其传递给callback
 */
EventBus.on(events, priority, callback, that)
 
/**
 * 注册只执行一次的事件监听器
 * @param events: string | string[] 事件名称(s)
 * @param priority?: number 优先级,默认1000
 * @param callback: Function 回调函数
 * @param that?: object 上下文中的this,会将其传递给callback
 */
EventBus.once(events, priority, callback, that)
 
/**
 * 关闭、删除事件监听器
 * @param events
 * @param callback
 */
EventBus.off(events, callback)
 
/**
 * 创建一个可以让EventBus识别的事件,并返回这个事件
 * @param data: object
 * @return event: object
 */
EventBus.createEvent(data)

/**
 * 主动触发事件
 * @param type: string | object 事件名称/嵌套事件名称的对象({type: string})
 * @param data: any 传递给回调函数的参数 
 * @return status: boolean | any 事件返回值(如果指定);如果侦听器阻止了默认操作,则返回false
 */
EventBus.fire(type, data)

6. InternalEvent 事件

指通过事件总线发出来的事件实例。

/**
 * A event that is emitted via the event bus.
 */
function InternalEvent() { }

// 阻止事件传播给其他接收者
InternalEvent.prototype.stopPropagation = function() {
  this.cancelBubble = true;
};

// 阻止事件的默认结果
InternalEvent.prototype.preventDefault = function() {
  this.defaultPrevented = true;
};

InternalEvent.prototype.init = function(data) {
  assign(this, data || {});
};

事件可以结合事件总线对事件监听器做自定义处理。

比如:

EventBus.on("foo", function(event) {
    console.log(event.type) // "foo"
    event.stopPropagation(); // 停止事件继续传播
    event.preventDefault(); // 阻止事件原来的返回值,返回false
})

// 传入自定义信息
const payload = { fooPayload: "foo" }
EventBus.on("foo", function(event, payload) {
    console.log(payload) // { fooPayload: "foo" }
})

Unicode特殊符号

summary_start
记录一下可能用到的unicode编码
summary_end

⇠ 箭头类

符号UNICODE符号UNICODE
HTMLJSCSSHTMLJSCSS
&#8672 \u21E0 \21E0 &#8674 \u21E2 \21E2
&#8673 \u21E1 \21E1 &#8675 \u21E3 \21E3
&#8606 \u219E \219E &#8608 \u21A0 \21A0
&#8607 \u219F \219F &#8609 \u21A1 \21A1
&#8592 \u2190 \2190 &#8594 \u2192 \2192
&#8593 \u2191 \2191 &#8595 \u2193 \2193
&#8596 \u2194 \2194 &#8597 \u2195 \2195
&#8644 \u21C4 \21C4 &#8645 \u21C5 \21C5
&#8610 \u21A2 \21A2 &#8611 \u21A3 \21A3
&#8670 \u21DE \21DE &#8671 \u21DF \21DF
&#8619 \u21AB \21AB &#8620 \u21AC \21AC
&#8668 \u21DC \21DC &#8669 \u21DD \21DD
&#8602 \u219A \219A &#8603 \u219B \219B
&#8622 \u21AE \21AE &#8621 \u21AD \21AD
&#8678 \u21E6 \21E6 &#8680 \u21E8 \21E8
&#8679 \u21E7 \21E7 &#8681 \u21E9 \21E9
&#9650 \u25B2 \25B2 &#9658 \u25BA \25BA
&#9660 \u25BC \25BC &#9668 \u25C4 \25C4
&#10132 \u2794 \2794 &#10137 \u2799 \2799
&#10152 \u27A8 \27A8 &#10162 \u27B2 \27B2
&#10140 \u279C \279C &#10142 \u279E \279E
&#10143 \u279F \279F &#10144 \u27A0 \27A0
&#10148 \u27A4 \27A4 &#10149 \u27A5 \27A5
&#10150 \u27A6 \27A6 &#10151 \u27A7 \27A7
&#10165 \u27B5 \27B5 &#10168 \u27B8 \27B8
&#10172 \u27BC \27BC &#10173 \u27BD \27BD
&#10170 \u27BA \27BA &#10163 \u27B3 \27B3
&#8631 \u21B7 \21B7 &#8630 \u21B6 \21B6
&#8635 \u21BB \21BB &#8634 \u21BA \21BA
&#8629 \u21B5 \21B5 &#8623 \u21AF \21AF
&#10174 \u27BE \27BE

❤ 基本形状类

符号UNICODE符号UNICODE
HTMLJSCSSHTMLJSCSS
&#10084 \u2764 \2764 &#9992 \u2708 \2708
&#9733 \u2605 \2605 &#10022 \u2726 \2726
&#9728 \u2600 \2600 &#9670 \u25C6 \25C6
&#9672 \u25C8 \25C8 &#9635 \u25A3 \25A3
&#9787 \u263B \263B &#9786 \u263A \263A
&#9785 \u2639 \2639 &#9993 \u2709 \2709
&#9742 \u260E \260E &#9743 \u260F \260F
&#9990 \u2706 \2706 &#65533 \uFFFD \FFFD
&#9729 \u2601 \2601 &#9730 \u2602 \2602
&#10052 \u2744 \2744 &#9731 \u2603 \2603
&#10056 \u2748 \2748 &#10047 \u273F \273F
&#10048 \u2740 \2740 &#10049 \u2741 \2741
&#9752 \u2618 \2618 &#10086 \u2766 \2766
&#9749 \u9749 \9749 &#10050 \u2742 \2742
&#9765 \u2625 \2625 &#9774 \u262E \262E
&#9775 \u262F \262F &#9770 \u262A \262A
&#9764 \u2624 \2624 &#9988 \u2704 \2704
&#9986 \u2702 \2702 &#9784 \u2638 \2638
&#9875 \u2693 \2693 &#9763 \u2623 \2623
&#9888 \u26A0 \26A0 &#9889 \u26A1 \26A1
&#9762 \u2622 \2622 &#9851 \u267B \267B
&#9855 \u267F \267F &#9760 \u2620 \2620

货币类

符号UNICODE符号UNICODE
HTMLJSCSSHTMLJSCSS
$ &#36 \u0024 \0024 ¢ &#162 \u00A2 \00A2
£ &#163 \u00A3 \00A3 ¤ &#164 \u00A4 \00A4
&#8364 \u20AC \20AC ¥ &#165 \u00A5 \00A5
&#8369 \u20B1 \20B1 &#8377 \u20B9 \20B9

数学类

符号UNICODE符号UNICODE
HTMLJSCSSHTMLJSCSS
½ &#189 \u00BD \00BD ¼ &#188 \u00BC \00BC
¾ &#190 \u00BE \00BE &#8531 \u2153 \2153
&#8532 \u2154 \2154 &#8539 \u215B \215B
&#8540 \u215C \215C &#8541 \u215D \215D
&#8240 \u2030 \2030 % &#37 \u0025 \0025
< &#60 \u003C \003C > &#62 \u003E \003E

♫ 音乐符号类

符号UNICODE符号UNICODE
HTMLJSCSSHTMLJSCSS
&#9833 \u2669 \2669 &#9834 \u266A \266A
&#9835 \u266B \266B &#9836 \u266C \266C
&#9837 \u266D \266D &#9839 \u266F \266F

✖ 对错号

符号UNICODE符号UNICODE
HTMLJSCSSHTMLJSCSS
&#160 \u00A0 \00A0 &#9744 \u2610 \2610
&#9745 \u2611 \2611 &#9746 \u2612 \2612
&#10003 \u2713 \2713 &#10004 \u2714 \2714
&#10005 \u10005 \10005 &#10006 \u2716 \2716
&#10007 \u2717 \2717 &#10008 \u2718 \2718

★ 全都是星星

符号UNICODE符号UNICODE
HTMLJSCSSHTMLJSCSS
&#9733 \u2605 \2605 &#10029 \u272D \272D
&#10030 \u272E \272E &#9734 \u2606 \2606
&#10026 \u272A \272A &#10017 \u2721 \2721
&#10031 \u272F \272F &#10037 \u2735 \2735
&#10038 \u2736 \2736 &#10040 \u2738 \2738
&#10041 \u2739 \2739 &#10042 \u273A \273A
&#10033 \u2731 \2731 &#10034 \u2732 \2732
&#10036 \u2734 \2734 &#10035 \u2733 \2733
&#10043 \u273B \273B &#10045 \u273D \273D
&#10059 \u274B \274B &#10054 \u2746 \2746
&#10052 \u2744 \2744 &#10053 \u2745 \2745

♚ 国际象棋类

符号UNICODE符号UNICODE
HTMLJSCSSHTMLJSCSS
&#9818 \u265A \265A &#9819 \u265B \265B
&#9820 \u265C \265C &#9821 \u265D \265D
&#9822 \u265E \265E &#9823 \u265F \265F
&#9812 \u2654 \2654 &#9813 \u2655 \2655
&#9814 \u2656 \2656 &#9815 \u2657 \2657
&#9816 \u2658 \2658 &#9817 \u2659 \2659

扑克牌类

符号UNICODE符号UNICODE
HTMLJSCSSHTMLJSCSS
&#9824 \u2660 \2660 &#9827 \u2663 \2663
&#9829 \u2665 \2665 &#9830 \u2666 \2666
&#9828 \u2664 \2664 &#9831 \u2667 \2667
&#9825 \u2661 \2661 &#9826 \u2662 \2662

& 希腊字母

符号UNICODE符号UNICODE
HTMLJSCSSHTMLJSCSS
Α &#913 \u0391 \0391 Β &#914 \u0392 \0392
Γ &#915 \u0393 \0393 Δ &#916 \u0394 \0394
Ε &#917 \u0395 \0395 Ζ &#918 \u0396 \0396
Η &#919 \u0397 \0397 Θ &#920 \u0398 \0398
Ι &#921 \u0399 \0399 Κ &#922 \u039A \039A
Λ &#923 \u039B \039B Μ &#924 \u039C \039C
Ν &#925 \u039D \039D Ξ &#926 \u039E \039E
Ο &#927 \u039F \039F Π &#928 \u03A0 \03A0
Ρ &#929 \u03A1 \03A1 Σ &#931 \u03A3 \03A3
Τ &#932 \u03A4 \03A4 Υ &#933 \u03A5 \03A5
Φ &#934 \u03A6 \03A6 Χ &#935 \u03A7 \03A7
Ψ &#936 \u03A8 \03A8 Ω &#937 \u03A9 \03A9

法律符号

符号UNICODE符号UNICODE
HTMLJSCSSHTMLJSCSS
® &#174 \u00AE \00AE © &#169 \u00A9 \00A9
&#8471 \u2117 \2117 &#153 \u0099 \0099
&#8480 \u2120 \2120

@ 标点和符号

符号UNICODE符号UNICODE
HTMLJSCSSHTMLJSCSS
« &#171 \u00AB \00AB » &#187 \u00BB \00BB
&#139 \u008B \008B &#155 \u009B \009B
&#8220 \u201C \201C &#8221 \u201D \201D
&#8216 \u2018 \2018 &#8217 \u2019 \2019
&#8226 \u2022 \2022 &#9702 \u25E6 \25E6
¡ &#161 \u00A1 \00A1 ¿ &#191 \u00BF \00BF
&#8453 \u2105 \2105 &#8470 \u2116 \2116
& &#38 \u0026 \0026 @ &#64 \u0040 \0040
&#8478 \u211E \211E &#8451 \u2103 \2103
&#8457 \u2109 \2109 ° &#176 \u00B0 \00B0
| &#124 \u007C \007C ¦ &#166 \u00A6 \00A6
&#8211 \u2013 \2013 &#8212 \u2014 \2014
&#8230 \u2026 \2026 &#182 \u00B6 \00B6
&#8764 \u223C \223C &#8800 \u2260 \2260

ES历史版本特性汇总之ES7&8

summary_start
本文主要汇总ES7和ES8版本发布的新特性。
summary_end

ES7新特性(2016)

相对于ES6 的标准化改动量来说,ES7只增加了两个新特性

1. includes()

Array.prototype.includes()函数用来判断一个数组是否包含指定的元素,如果包含则返回ture,否则返回false。

includes()函数与indexOf()函数相似。

let arr: number[] = [1,2,3,4,5];
arr.includes(1) // return true
arr.indexOf(2) => 1

在ES7之前,我们判断数组中是否包含一个元素通常是判断数组的indexOf返回值是否为-1

example
let arr: number[] = [1,2,3,4,5];
if (arr.indexOf(1) ! == -1 ) {
	console.info("数组包含1");
} else {
	console.info("数组不包含1");
}

在ES7之后,我们可以直接使用includes来判断,更简单直观。

example
let arr: number[] = [1,2,3,4,5];
if (arr.includes(1)) {
	console.info("数组包含1");
} else {
	console.info("数组不包含1");
}

2. 指数操作符

ES7 引入了指数运算符**,与Math.pow()等效

example
console.log(math.pow(2, 10)) // 1024
console.log(2**10) // 1024

ES8新特性(2017)

与ES6相比,ES8仍是EcmaScript的一个小版本更新,引入了如下功能。

  1. Object.values,
  2. Object.entries,
  3. Object.getownPropertyDescriptors(),
  4. 字符串填充(padStart和padEnd)
  5. Async/await异步函数
  6. 共享内存和Atomics

Object.values()

Object.values()返回一个包含所有对象属性的数组,但只包括对象自身的属性,不包括继承的值。

const company: Object = { name: "cisdi", address: "Yubei, Chongqing, China", type: "IT"};
Object.values(company) // ["cisdi", "Yubei, Chongqing, China", "IT"]

此方法也适用于数组,返回值与原数组相同。

const company: String[] = ["cisdi", "IT"];
Object.values(company) // ["cisdi", "IT"]

在ES8之前不使用Object.values获取对象所有的值采用下面的方法。

const company: Object = { name: "cisdi", address: "Yubei, Chongqing, China", type: "IT"};
const values: String[] = Object.keys(company).map((key: string) => company[key]);
console.log(values) // ["cisdi", "Yubei, Chongqing, China", "IT"]

Object.entries()

Object.entries()返回一个包含对象所有属性的二维数组,以[key, value]形式作为单个数组元素。

const company: Object = { name: "cisdi", address: "Yubei, Chongqing, China", type: "IT"};
Object.entries(company) // [["name", "cisdi"], ["address", "Yubei, Chongqing, China"], ["type", "IT"]]

此方法同样适用于数组,返回一个由数组下标作为键名key的二维数组。

const company: String[] = ["cisdi", "IT"];
Object.entries(company) // [[0, "cisdi"], [1, "IT"]]

使用此方法遍历对象中所有属性的key和value。

const company: Object = { name: "cisdi", address: "Yubei, Chongqing, China", type: "IT"};
for(let [key,value] of Object.entries(company)){
	console.info(`key: ${key}, value: ${value}`);
})

Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptors()返回一个对象所有属性的描述符。

字符串填充

Friends

阁子

link: //newdee.cf/
cover: //i.loli.net/2018/12/15/5c14f329b2c88.png
avatar: //i.loli.net/2018/12/15/5c14f3299c639.jpg

后宫学长

link: //haremu.com/
cover: //i.loli.net/2018/12/15/5c14f38ce5543.png
avatar: //i.loli.net/2018/12/15/5c14f38ccd4fd.jpg

⊿ 叶之色 ☆ ~

link: http://leaful.com/
cover: //i.loli.net/2018/12/15/5c14f3bcf3fab.png
avatar: //cn.gravatar.com/avatar/e583fd49db419a074831924d221adc1b?s=40&d=mm&r=g

惶心博客

link: //www.justhx.com/
cover: //i.loli.net/2019/04/14/5cb2b2ee30647.jpg
avatar: //i.loli.net/2019/04/15/5cb4663545c28.jpg

Blessing Studio

link: //blessing.studio/
cover: //i.loli.net/2018/12/15/5c14f42604576.png
avatar: //i.loli.net/2018/12/15/5c14f42605f8e.jpg

梓喵出没

link: //www.azimiao.com/
cover: //i.loli.net/2018/12/15/5c14f45622a3e.png
avatar: //i.loli.net/2018/12/15/5c14f456248f1.jpeg

稗田千秋

link: //wind.moe/
cover: //i.loli.net/2018/12/15/5c14f580c5b39.png
avatar: //i.loli.net/2018/12/15/5c14f580b5691.jpg

痴情的小五

link: //cherryml.com/
cover: //i.loli.net/2018/12/15/5c14f54deafdf.png
avatar: //i.loli.net/2018/12/15/5c14f54dd33e4.jpg

林洋洋

link: //linyy.name/
cover: //i.loli.net/2018/12/15/5c14f5182c813.jpg
avatar: //i.loli.net/2018/12/15/5c14f5180cdbc.jpg

樱花庄的白猫

link: //2heng.xin/
cover: //i.loli.net/2018/12/15/5c14f4e70e82c.jpg
avatar: //i.loli.net/2018/12/15/5c14f4e6dbfdd.jpg

Makito's Notebook

link: //blog.keep.moe
cover://i.loli.net/2018/12/15/5c14f4b74cc1b.png
avatar: //i.loli.net/2018/12/15/5c14f4b745235.jpg

维基萌

link: //www.wikimoe.com/
cover: //i.loli.net/2018/12/15/5c14f487371e6.png
avatar: //i.loli.net/2018/12/15/5c14f48735383.jpg

岁月小筑

link: //cily.cc/
cover: //i.loli.net/2018/12/15/5c14f2e81d82b.png
avatar: //i.loli.net/2018/12/15/5c14f2e81bb17.jpg

MIKUSA の小站

link: //www.himiku.com
cover: //i.loli.net/2018/12/15/5c14f2390c3bd.png
avatar: //i.loli.net/2018/12/15/5c14f1bb58a6d.jpg

禾雀飛翔

link: //buncho.moe/
cover: //i.loli.net/2018/12/23/5c1f7e7e2c9ba.jpg
avatar: //i.loli.net/2018/12/23/5c1f7dc2938e8.jpeg

阿硕の博客

link: //www.sshuo.cc/
cover: //i.loli.net/2018/12/23/5c1f82225e07d.jpg
avatar: //i.loli.net/2018/12/23/5c1f82115bb6c.jpg

陶心昊の数据存储系统

link: //taoxinhao.cn/
cover: //i.loli.net/2018/12/30/5c289d5e3248e.png
avatar: //i.loli.net/2018/12/30/5c289c5e8aece.jpg

风执行

link: //gleehub.com/
cover: //i.loli.net/2018/12/30/5c28b19fb7bb0.jpg
avatar: //i.loli.net/2018/12/30/5c28b19f83b23.jpg

Hans362

link: //blog.hans362.cn
cover: //i.loli.net/2019/01/08/5c34775058aa5.png
avatar: //i.loli.net/2019/01/08/5c34769c98720.jpg

Cat

link: //www.zhudm.com
cover: //i.loli.net/2019/01/10/5c36a37854b6e.jpg
avatar: //i.loli.net/2019/01/10/5c36a3674a810.jpeg

可乐加点冰

link: //cokewithice.com/links.html
cover: //i.loli.net/2019/01/25/5c4ae1c80a9d3.jpg
avatar: //i.loli.net/2019/01/25/5c4ae13c35033.jpg

椎咲良田

link: //sanshiliuxiao.top
cover: //i.loli.net/2019/01/28/5c4ed2421e164.jpg
avatar: //i.loli.net/2019/01/28/5c4eca46c1d0b.png

小代理の记事薄

link: //lolioi.moe/
cover: //i.loli.net/2019/03/13/5c88b6b4c4fa5.jpg
avatar: //i.loli.net/2019/03/19/5c905d3c5b2e9.jpg

东方幻梦

link: //blog.dfhm.me/
cover: //i.loli.net/2019/03/17/5c8dc57411680.jpg
avatar: //i.loli.net/2019/03/17/5c8dc57407550.jpg

時雨

link: //shiyu.host/
cover: //i.loli.net/2019/03/17/5c8dcd23da60e.jpg
avatar: //i.loli.net/2019/03/17/5c8dcd37b3e7b.jpg

Broca-Phenol

link: //phenol-phthalein.info/
cover: //i.loli.net/2019/03/17/5c8e0b0e536c9.jpg
avatar: //i.loli.net/2019/03/17/5c8e0b0e49e9b.jpg

~ 魔法使之家 ~

link: //blog.ero.ink
cover: //i.loli.net/2019/03/17/5c8e5954cebb9.jpg
avatar: //i.loli.net/2019/03/17/5c8e5954b8c5f.jpeg

恶魔菌の记事簿

link: //meowqvq.wordpress.com
cover: //i.loli.net/2019/03/18/5c8f6a8403b8a.jpg
avatar: //i.loli.net/2019/03/18/5c8f6a83ed7c4.png

SimonKing

link: //kurumi.ink
cover: //i.loli.net/2019/03/19/5c904e4fef87b.jpg
avatar: //i.loli.net/2019/03/19/5c904e4fda66b.jpg

极束梦想

link: //www.eendtech.com
cover: //i.loli.net/2019/03/19/5c905cf9695d8.jpg
avatar: //i.loli.net/2019/03/19/5c905cf94da79.jpg

亿林

link: //minemine.cc
cover: //i.loli.net/2019/03/19/5c90c217b0e6d.jpg
avatar: //i.loli.net/2019/03/19/5c90c2179db40.jpeg

青花鱼

link: //zankyo.cc
cover: //i.loli.net/2019/03/26/5c9a06443c6c8.jpg
avatar: //i.loli.net/2019/03/26/5c9a06445fa16.jpg

Rin404°

link: //www.rin404.com
cover: //i.loli.net/2019/04/13/5cb1ccfd3e1f2.jpg
avatar: //i.loli.net/2019/04/13/5cb1ccfd333aa.jpeg

宅日记

link: //crosschannel.cc
cover: //i.loli.net/2019/05/07/5cd1405264986.jpg
avatar: //i.loli.net/2019/05/07/5cd1410469b58.jpg

Mikukonai

link: //mikukonai.com
cover: //i.loli.net/2019/05/14/5cdab9fe65d3169815.jpg
avatar: //i.loli.net/2019/05/14/5cdaba7dca4ed41206.jpg

初始化React + TypeScript项目

summary_start
出于我对react的强烈好奇,我终于开始了react的基础学习。百度了一下发现大部分都是直接采用create-react-app来初始化项目的,但是因为之前用的Vue也需要配置webpack,so...
summary_end

1. 建立项目并初始化

找到你的工作区文件夹,新建一个项目文件夹,然后初始化你的项目。

// mkdir [你的项目名称]
mkdir react-ts-demo
cd react-ts-demo
yarn init

2. 安装开发依赖包

I. 安装React,ReactDom依赖

yarn add react
yarn add react-dom
// 可以写在一行,同时引入
// yarn add react react-dom

II. 安装React类型库

由于React是用js和jsx文件写的,需要引入类型库来识别ts/tsx文件

yarn add --dev @types/react @types/react-dom
// 使用--dev,将依赖包安装到开发环境devDependencies下

III. 安装ts

yarn add --dev typescript ts-loader

IV. 安装webpack

yarn add --dev webpack webpack-cli webpack-dev-server webpack-merge

V. 安装webpack插件

yarn add --dev source-map-loader html-webpack-plugin clean-webpack-plugin uglifyjs-webpack-plugin

3. 项目配置

在根目录下新建config文件夹,存放项目配置文件。

I. 配置通用文件路径paths.js

// path: ./config/path.js
const path = require("path");
const fs = require("fs");

const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);

module.exports = {
  appBuild: resolveApp("build"),
  appPublic: resolveApp("public"),
  appIndex: resolveApp("src/index.tsx"),
  appHtml: resolveApp("public/index.html")
};

II. 通用webpack配置webpack.config.js

在config下新建webpack文件夹,存放webpack配置文件。

// path: ./config/webpack/webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
const paths = require("../paths");

module.exports = {
  resolve: {
    extensions: [".tsx", ".ts", ".js", ".json"]
  },
  devtool: "source-map",
  module: {
    rules: [
      {
        enforce: "pre",
        test: /\.js$/,
        loader: "source-map-loader"
      },
      {
        test: /\.(ts|tsx)$/,
        loader: "ts-loader"
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "React + Typescript Demo",
      favicon: "./public/logo.ico",
      template: paths.appHtml,
      inject: false
    })
  ]
};

III. 开发环境webpack配置webpack.config.dev.js

const webpack = require("webpack");
const merge = require("webpack-merge");

const paths = require("../paths");
const commonConfig = require("./webpack.config.common");

module.exports = merge.smart(commonConfig, {
  mode: "development",
  entry: paths.appIndex, // 入口文件
  output: {
    filename: "static/js/[name]-bundle-[hash:8].js"
    // 使用hash命名
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin()
  ],
  devServer: {
    // 开启前端路径回路映射,子路径映射到根路径,由前端路由框架来解析
    // historyApiFallback: true,
    // 关闭 Host 检查,同网段其他设备,可通过内网 IP 访问本机服务(需要配合 host: '0.0.0.0')使用
    // disableHostCheck: true,
    // host: "0.0.0.0",
    port: "8000",
    inline: true,
    hot: true
    // 请求代理服务
    // proxy: {
    //   "/api": {
    //     // 这里改为项目后端 API 接口 Host
    //     target: "http://server.address:post",
    //     // 支持跨域调用
    //     changeOrigin: true
    //   }
    // }
  }
});

IV. 生产环境webpack配置webpack.config.build.js

即打包配置。

const merge = require("webpack-merge");
const CleanWebpackPlugin = require("clean-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");

const paths = require("../paths");
const commonConfig = require("./webpack.config.common");

module.exports = merge.smart(commonConfig, {
  mode: "production",
  entry: paths.appIndex,
  output: {
    path: paths.appBuild,
    filename: "static/js/[name]-[hash:8].js"
  },
  optimization: {
    minimizer: [
      // 压缩 js
      new UglifyJsPlugin({
        cache: true,
        parallel: true,
        sourceMap: true
      })
    ],
    splitChunks: {
      // 切割代码块,提取为独立的 chunk 文件
      chunks: "all"
    }
  },
  plugins: [
    // 每次编译之前,清空上一次编译的文件
    new CleanWebpackPlugin([paths.appBuild], {
      root: process.cwd()
    })
  ]
});

V. tsconfig.json 配置

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true, // 允许合成默认导出
    "jsx": "react",
    "lib": ["es6", "dom"], // 若使用更高版本的ES,可以在此次改
    "module": "esnext",
    "moduleResolution": "node",
    "noImplicitAny": true,
    "rootDir": "src",
    "sourceMap": true,
    "strict": true,
    "target": "es5"
  },
  "exclude": ["node_modules", "build"]
}

4. 创建入口文件

I. 在public文件夹下新建index.html文件。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />
    <title><%= htmlWebpackPlugin.options.title %></title>
    <link rel="icon" href="/<%= htmlWebpackPlugin.options.favicon %>" />
    <% for (let css in htmlWebpackPlugin.files.css) { %>
    <link href="/<%=htmlWebpackPlugin.files.css[css] %>" rel="stylesheet" />
    <% } %>
  </head>
  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>
    <% for (let chunk in htmlWebpackPlugin.files.chunks) { %>
    <script
      type="text/javascript"
      src="/<%=htmlWebpackPlugin.files.chunks[chunk].entry %>"
    ></script>
    <% } %>
  </body>
</html>

II. 在public文件夹下引入项目网站favicon:logo.ico

III. 在src文件夹下新建index.tsx文件

import React from "react";
import ReactDOM from "react-dom";

ReactDOM.render(<div>Hello world!</div>, document.getElementById("root"));

5. 添加项目启动脚本

在package.json文件内添加script运行命令。

{
	"script": {
		"start": "webpack-dev-server --open --colors --config config/webpack/webpack.config.dev.js",
		"build": "webpack --progress --colors --config config/webpack/webpack.config.build.js"
	}
}

6. 启动项目

在根目录下运行

yarn start

前端学习记录——基础篇(一)

summary_start
日常学习记录。
summary_end

1. MVVM(Model-View-View-Model)模式

要编写可维护的前端代码绝非易事。我们已经用MVC(MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写)模式通过koa(Express的下一代基于Node.js的web框架)实现了后端数据、模板页面和控制器的分离,但是,对于前端来说,还不够。

  1. 很早之前,页面全是静态网页,预先编写html存放在web服务器,浏览器请求时直接返回html文件;
  2. 之后,由于需要针对不同的用户显示不同的页面,于是需要服务器针对不同的用户生成不同的页面,个最直接的想法就是利用C、C++这些编程语言,直接向浏览器输出拼接后的字符串。这种技术被称为CGI:Common Gateway Interface;
  3. 再之后,由于新浪之类的复杂页面不能通过拼接字符串完成,在拼字符串的时候,大多数字符串都是HTML片段,是不变的,变化的只有少数和用户相关的数据,所以,又出现了新的创建动态HTML的方式:ASP、JSP和PHP——分别由微软、SUN和开源社区开发。在ASP中,一个asp文件就是一个HTML,但是,需要替换的变量用特殊的<%=var%>标记出来了,再配合循环、条件判断,创建动态HTML就比CGI要容易得多;这种情况如果要更新页面信息,就只能刷新页面重新向服务器请求。
  4. 后来,浏览器引入JavaScript之后,js可以对页面进行一些修改。JavaScript还可以通过修改HTML的DOM结构和CSS来实现一些动画效果,而这些功能没法通过服务器完成,必须在浏览器实现。用JavaScript在浏览器中操作HTML,经历了若干发展阶段:
    • 第一阶段,直接用JavaScript操作DOM节点,使用浏览器提供的原生API
    • 第二阶段,由于原生API不好用,还要考虑浏览器兼容性,jQuery横空出世,以简洁的API迅速俘获了前端开发者的芳心
    • 第三阶段,MVC模式,需要服务器端配合,JavaScript可以在前端修改服务器渲染后的数据
    • 现在,MVVM模式,由微软提出,鉴了桌面应用程序的MVC**,在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。把Model和View关联起来的就是ViewModel。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。

目前常用MVVM框架:React,Angular,Vue,另外还有Backbone,Ember等,但入门困难。

1.1 单向绑定

单向绑定: 把Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新

在Vue中,可以直接写{{ name }}绑定某个属性。如果属性关联的是对象,还可以用多个.引用,例如:

{{ address.zipcode }}

另一种单向绑定的方法是使用Vue的指令v-text,写法如下:

Hello, !

这种写法是把指令写在HTML节点的属性上,它会被Vue解析,该节点的文本内容会被绑定为Model的指定属性,注意不能再写双花括号{{ }}

1.2 双向绑定

双向绑定:用户更新了View,Model的数据也自动被更新了,这种情况就是双向绑定。

example:
填写表单就是一个最直接的例子。当用户填写表单时,View的状态就被更新了,如果此时MVVM框架可以自动更新Model的状态,那就相当于我们把Model和View做了双向绑定

vue 双向绑定:

// 1.创建vm实例
$(function() {
	var vm = new Vue({
		...
        data: {
        	email: "",
        	name: ""
        },
        methods: {
        	register: function {
        		// 显示JSON格式model
        		console.info(JSON.stringify(this.$data));
        		// TODO: AJAX.post
        	}
        }
	})
	window.vm = vm;
})
<!-- 编写表单 -->
<form id="vm" action="server src" v-on:submit.prevent="register">
    <p><input v-model="email"></p>
    <p><input v-model="name"></p>
</form>

我们可以在表单中输入内容,然后在浏览器console中用window.vm.$data查看Model的内容,也可以用window.vm.name查看Model的name属性,它的值和FORM表单对应的input标签是一致的。

如果在浏览器console中用JavaScript更新Model,例如,执行window.vm.name='Bob',表单对应的input输入框内容就会立刻更新。

v-on:submit="register"指令就会自动监听表单的submit事件,并调用register方法处理该事件。使用.prevent表示阻止事件冒泡,这样,浏览器不再处理form表单内的submit事件。在register函数中,我们将数据通过ajax发送给服务器,这样就完成了注册功能。

1.3 DOM同步

使用MVVM,当我们更新Model时,DOM结构会随着Model的变化而自动更新。

在Vue中,可以使用v-for指令来实现:

<ol>
    <li v-for="t in todos">
        <dl>
            <dt>{{ t.name }}</dt>
            <dd>{{ t.description }}</dd>
        </dl>
    </li>
</ol>

v-for指令把数组和一组li元素绑定了。在li元素内部,用循环变量t引用某个属性,例如,{{ t.name }}。这样,我们只关心如何更新Model,不关心如何增删DOM节点,大大简化了整个页面的逻辑。

2 Vue讲解

2.1 模板语法

1. 文本

Vue数据绑定常见形式:“mustache”语法(双大括号),v-text指令

<tempalte>
	<div>
		<p>hello {{ word }}</p>
		<p v-text="'hello' + word"></p>
		<p>{{`hello${ word }`}}</p>
	<div>
</template>

<script>
export default {
     data () {
         return {
              world : "world"
         }
     }
}
</script>

2. v-once

通过指令我们可以对文本值进行一次性赋值操作,只进行第一次的数据渲染,如果再次改变值,文本值也不会改变。

3. 纯html

我们在解析的不是文件而是一个html格式的时候放在v-text中或者"{{ }}"就会被当作一个文本解析,所以我们此时要用v-html指令进行解析,在1.0中支持"{{{ }}}"这种格式,为了防止xss功击,去除了这个功能。

<template>
    <div>
        <p v-html='html'></p>
     </div>
</template>

<script>
export default {
     data () {
         return {
              html : `<span style='color : red;'>显示红色的字你就解析成功了</span>`
         }
     }
}
</script>

4. 属性

用指令去解析,那就是v-bind:*,同时我们可以简写用v-bind语法糖 :即可。

我们在属性中支持number、string、boolean类型,以上显示能在界面中看出都能正常进行和原本属性所预期的,不用:来绑定的属性可以直接属性赋值,如果一定要通过data数据选项中返回的值一定要加 :

5. 使用JavaScript表达式

在业务场景中一些方法判断或者简单的过滤,那我们可以用javascript表达式,能让代码更简洁,更清晰,例如三元表达式,filter语法等

6. 修饰符

修饰符(Modifiers)是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。官方文档有详细解说每一个修饰赋的具体用途

7. 再次提示主逻辑代码都是写在.App.vue中,所有其它的组件代码都是写在componentes里

正常的情况在一个单个组件内部自己使用v-on的事件,都不会有问题,如果在一个组件上定义一个指令事件,必须要用.native

8. 列表渲染

  1. template v-for 模板渲染

使用template标签来渲染多个元素块,同时避免创建多个dom节点。

<template>
     <div>
         <template v-for="item in list">
              <p>{{item.content}}</p>
              <img :src="item.img" alt="">
              <p class="divider"></p>
         </template>
     </div>
</template>
  1. Object v-for 对象渲染

用 v-for 通过一个对象的属性来迭代。

<template>
     <table>
         <template>
            <tr>
                <td v-for="(value,key) in memberDetail" :key="key">{{key}}</td>
            </tr>
            <tr>
                <td v-for="(value,key) in memberDetail" :key="key">{{value}}</td>
            </tr>
         </template>
     </table>
</template>

<script>
export default {
     created () {
        //比方说我们这里拿到前面的custId传给后台拿到用户数据
        this.memberDetail = {
                 name : 'ziksang',
                 age : 20,
                 address : "xxx省xxxx市",
                 tel : "15921898427"
             }
     },
     data () {
         return {
             memberDetail : {} 
         }
     }
}
</script>
<style>
body,html{
    width:100%;
    height:100%
}
.divider{
    width:100%;
    height:1px;
    background:black;
}
</style>
  1. component v-for 组件渲染

  2. 数组更新检测

    • 数组变异:用Array.prototype里提供的原型方法里我们能直接改掉data选项里的数据,触发了视图更新(例如:push,pop,shift,unshift,splice等)
    • 数组非变异:能通过Array.prototype里的原形方法改变data选项artList数组触发视图改变的方法就是非变异方法,其余的方法都是操作后,形成一个返回值,所有操作只是返回了一个新数组,而不会触发视图更新(1.filter(), 2.concat(), 3.slice(), 4.map())
  3. 注意事项:

由于 JavaScript 的限制, Vue 不能检测以下变动的数组:
当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength

作用域和闭包

summary_start
...
summary_end

作用域

编译原理

传统语言的编译流程:
分词/词法分析(T/L) -- 解析/语法分析(Parsing) -- 代码生成
JavaScript引擎的编译过程要复杂的多,比如在语法分析和代码生成阶段有特定步骤来优化性能,包括对冗余元素进行优化。

理解作用域

人物:

  • 引擎:负责整个JavaScript程序的编译及执行过程。
  • 编译器:负责语法分析及代码生成等。
  • 作用域 负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。

对话:比如执行var a = 2
该变量赋值的过程会执行两个动作:编译器在当前作用域中声明一个变量;运行时引擎在作用域中查找该变量,找到就对其赋值。

编译器

引擎的变量查询方式:LHSRHS
LHS:赋值操作左侧的查询,即查找变量容器的本身,已对其进行赋值;
RHS:retrieve his source value(取到他的源值),即查出变量当前的值。

引擎与作用域

function foo(a) { 
   console.log( a ); // 2 
} 
foo( 2 );

过程:
引擎:查询作用域,对foo进行RHS引用;
作用域:查找foo,返回给引擎;
引擎:执行foo,对参数a进行LHS引用;
作用域:查找a,返回给引擎;
引擎:对a进行赋值,继续执行,对console进行RHS引用,
作用域:查找console,返回给引擎;
引擎:继续执行,调用log函数,对a进行RHS引用;
作用域:查找a,返回
。。。

作用域嵌套

当一个块或函数嵌套在另一个块或函数中时,就发生了作用域嵌套。因此,在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到变量或者抵达最外层作用域(全局作用域)。

异常

如果 RHS 查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError异常。
相比之下,如果引擎执行LHS查询时查询所有嵌套作用域没有找到所需变量,就会在顶层作用域(全局作用域)创建一个具有该名称的变量(前提是在非严格模式下);如果是严格模式,同样会抛出类似ReferenceError的异常。

词法作用域

词法阶段

简单来讲:词法作用域,即定义在词法阶段的作用域;换言之,就是由编辑代码将变量和块作用域写在哪里决定的。

function foo(a) { 
    var b = a * 2; 
 
    function bar(c) { 
        console.log( a, b, c ); 
    } 
 
    bar( b * 3 ); 
} 
 
foo( 2 ); // 2, 4, 12

这个例子中有三个逐级嵌套的作用域:

  1. 整个全局作用域,只有一个标识符foo;
  2. 包含foo所创建的作用域,有三个标识符a,bar,b
  3. 包含bar所创建的作用域,只有一个标识符c

作用域查找会在找到第一个匹配的标识符时停止

全局变量会自动成为全局对象(比如浏览器中的 window 对象)的属性,因此可以不直接通过全局对象的词法名称,而是间接地通过对全局对象属性的引用来对其进行访问: window.a

欺骗词法

“修改”词法作用域,即欺骗此法作用域,JavaScript有两种机制来实现这个目的,但是欺骗词法作用域会导致性能下降

eval

eval()函数可以接受一个字符串作为参数,并将其内容作为编辑代码时就存在于这个位置的代码。

function foo(str, a) { 
    eval( str ); // 欺骗! 
    console.log( a, b ); 
} 
 
var b = 2; 
 
foo( "var b = 3;", 1 ); // 1, 3

eval(..) 调用中的 var b = 3; 这段代码会被当作本来就在那里一样来处理。
代码执行时,会找到foo内部的a和b,执行console.log()。

eval(..) 通常被用来执行动态创建的代码。

JavaScript中,还有setTimeout()setInterval()可以实现类似效果,但已经过时,不提倡使用。
new Function()函数的行为也很类似,最后一个参数可以接受代码字符串,这个构建函数语法比eval()略微安全,但是也不提倡使用。

with

JavaScript 中另一个难以掌握(并且现在也不推荐使用)的用来欺骗词法作用域的功能是with 关键字。
with 通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。

var obj = { 
    a: 1, 
    b: 2, 
    c: 3 
}; 
 
// 单调乏味的重复 "obj" 
obj.a = 2; 
obj.b = 3; 
obj.c = 4; 
 
// 简单的快捷方式 
with (obj) { 
    a = 3; 
    b = 4; 
    c = 5; 
}

但是with有个副作用。

function foo(obj) { 
    with (obj) { 
        a = 2; 
    } 
} 
 
var o1 = { 
    a: 3 
}; 
 
var o2 = { 
    b: 3 
}; 
 
foo( o1 ); 
console.log( o1.a ); // 2 
 
foo( o2 ); 
console.log( o2.a ); // undefined 
console.log( a ); // 2——不好,a 被泄漏到全局作用域上了!

即实际上在 a = 2这一步创建了一个全局变量a。

eval(..) 函数如果接受了含有一个或多个声明的代码,就会修改其所处的词法作用域,而with 声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域。

严格模式下,with被完全禁止,eval()和with会被限制。

性能

eval(..) 和 with 会在运行时修改或创建新的作用域,以此来欺骗其他在书写时定义的词法作用域。

JavaScript在编译阶段会进行数项的性能优化。有些优化只能依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数定义的位置,才能在执行过程中快速找到标识符。

函数作用域和块作用域

函数作用域

含义:属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)

从源码角度再看数据绑定(转)

summary_start
转载
summary_end

数据绑定原理

前面已经讲过Vue数据绑定的原理了,现在从源码来看一下数据绑定在Vue中是如何实现的。

首先看一下Vue.js官网介绍响应式原理的这张图。

这张图比较清晰地展示了整个流程,首先通过一次渲染操作触发Data的getter(这里保证只有视图中需要被用到的data才会触发getter)进行依赖收集,这时候其实Watcher与data可以看成一种被绑定的状态(实际上是data的闭包中有一个Deps订阅者,在修改的时候会通知所有的Watcher观察者),在data发生变化的时候会触发它的setter,setter通知Watcher,Watcher进行回调通知组件重新渲染的函数,之后根据diff算法来决定是否发生视图的更新。

Vue在初始化组件数据时,在生命周期的beforeCreatecreated钩子函数之间实现了对data、props、computed、methods、events以及watch的处理。

initData

这里来讲一下initData,可以参考源码instance下的state.js文件,下面所有的中文注释都是我加的,英文注释是尤大加的,请不要忽略英文注释,英文注释都讲到了比较关键或者晦涩难懂的点。

加注释版的vue源码也可以直接通过传送门查看,这些是我在阅读Vue源码过程中加的注释,持续更新中。

initData主要是初始化data中的数据,将数据进行Observer,监听数据的变化,其他的监视原理一致,这里以data为例。

function initData (vm: Component) {

  /*得到data数据*/
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}

  /*判断是否是对象*/
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }

  // proxy data on instance
  /*遍历data对象*/
  const keys = Object.keys(data)
  const props = vm.$options.props
  let i = keys.length

  //遍历data中的数据
  while (i--) {
    /*保证data中的key不与props中的key重复,props优先,如果有冲突会产生warning*/
    if (props && hasOwn(props, keys[i])) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${keys[i]}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(keys[i])) {
      /*判断是否是保留字段*/

      /*这里是我们前面讲过的代理,将data上面的属性代理到了vm实例上*/
      proxy(vm, `_data`, keys[i])
    }
  }
  /*Github:https://github.com/answershuto*/
  // observe data
  /*从这里开始我们要observe了,开始对数据进行绑定,这里有尤大大的注释asRootData,这步作为根数据,下面会进行递归observe进行对深层对象的绑定。*/
  observe(data, true /* asRootData */)
}

其实这段代码主要做了两件事,一是将_data上面的数据代理到vm上,另一件事通过observe将所有数据变成observable。

proxy

接下来看一下proxy代理。

/*添加代理*/
export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

这里比较好理解,通过proxy函数将data上面的数据代理到vm上,这样就可以用app.text代替app._data.text了。

observe

接下来是observe,这个函数定义在core文件下observer的index.js文件中。

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
 /*
 尝试创建一个Observer实例(__ob__),如果成功创建Observer实例则返回新的Observer实例,如果已有Observer实例则返回现有的Observer实例。
 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
  /*判断是否是一个对象*/
  if (!isObject(value)) {
    return
  }
  let ob: Observer | void

  /*这里用__ob__这个属性来判断是否已经有Observer实例,如果没有Observer实例则会新建一个Observer实例并赋值给__ob__这个属性,如果已有Observer实例则直接返回该Observer实例*/
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (

    /*这里的判断是为了确保value是单纯的对象,而不是函数或者是Regexp等情况。*/
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {

    /*如果是根数据则计数,后面Observer中的observe的asRootData非true*/
    ob.vmCount++
  }
  return ob
}

Vue的响应式数据都会有一个__ob__的属性作为标记,里面存放了该属性的观察器,也就是Observer的实例,防止重复绑定。

Observer

接下来看一下新建的Observer。Observer的作用就是遍历对象的所有属性将其进行双向绑定。

/**
 * Observer class that are attached to each observed
 * object. Once attached, the observer converts target
 * object's property keys into getter/setters that
 * collect dependencies and dispatches updates.
 */
export class  {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0

    /*
    将Observer实例绑定到data的__ob__属性上面去,之前说过observe的时候会先检测是否已经有__ob__对象存放Observer实例了,def方法定义可以参考https://github.com/vuejs/vue/blob/dev/src/core/util/lang.js#L16
    */
    def(value, '__ob__', this)
    if (Array.isArray(value)) {

      /*
          如果是数组,将修改后可以截获响应的数组方法替换掉该数组的原型中的原生方法,达到监听数组数据变化响应的效果。
          这里如果当前浏览器支持__proto__属性,则直接覆盖当前数组对象原型上的原生数组方法,如果不支持该属性,则直接覆盖数组对象的原型。
      */
      const augment = hasProto
        ? protoAugment  /*直接覆盖原型的方法来修改目标对象*/
        : copyAugment   /*定义(覆盖)目标对象或数组的某一个方法*/
      augment(value, arrayMethods, arrayKeys)
      /*Github:https://github.com/answershuto*/
      /*如果是数组则需要遍历数组的每一个成员进行observe*/
      this.observeArray(value)
    } else {

      /*如果是对象则直接walk进行绑定*/
      this.walk(value)
    }
  }

  /**
   * Walk through each property and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)

    /*walk方法会遍历对象的每一个属性进行defineReactive绑定*/
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array<any>) {

    /*数组需要遍历每一个成员进行observe*/
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

Observer为数据加上响应式属性进行双向绑定。如果是对象则进行深度遍历,为每一个子对象都绑定上方法,如果是数组则为每一个成员都绑定上方法。

如果是修改一个数组的成员,该成员是一个对象,那只需要递归对数组的成员进行双向绑定即可。但这时候出现了一个问题:如果我们进行pop、push等操作的时候,push进去的对象根本没有进行过双向绑定,更别说pop了,那么我们如何监听数组的这些变化呢?
Vue.js提供的方法是重写push、pop、shift、unshift、splice、sort、reverse这七个数组方法。修改数组原型方法的代码可以参考observer/array.js以及observer/index.js

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that has this object as root $data

  constructor (value: any) {
    //.......

    if (Array.isArray(value)) {
      /*
          如果是数组,将修改后可以截获响应的数组方法替换掉该数组的原型中的原生方法,达到监听数组数据变化响应的效果。
          这里如果当前浏览器支持__proto__属性,则直接覆盖当前数组对象原型上的原生数组方法,如果不支持该属性,则直接覆盖数组对象的原型。
      */
      const augment = hasProto
        ? protoAugment  /*直接覆盖原型的方法来修改目标对象*/
        : copyAugment   /*定义(覆盖)目标对象或数组的某一个方法*/
      augment(value, arrayMethods, arrayKeys)

      /*如果是数组则需要遍历数组的每一个成员进行observe*/
      this.observeArray(value)
    } else {
      /*如果是对象则直接walk进行绑定*/
      this.walk(value)
    }
  }
}

/**
 * Augment an target Object or Array by intercepting
 * the prototype chain using __proto__
 */
 /*直接覆盖原型的方法来修改目标对象或数组*/
function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

/**
 * Augment an target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
/*定义(覆盖)目标对象或数组的某一个方法*/
function copyAugment (target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i]
    def(target, key, src[key])
  }
}
/*
 * not type checking this file because flow doesn't play well with
 * dynamically accessing methods on Array prototype
 */

import { def } from '../util/index'

/*取得原生数组的原型*/
const arrayProto = Array.prototype
/*创建一个新的数组对象,修改该对象上的数组的七个方法,防止污染原生数组方法*/
export const arrayMethods = Object.create(arrayProto)

/**
 * Intercept mutating methods and emit events
 */
 /*这里重写了数组的这些方法,在保证不污染原生数组原型的情况下重写数组的这些方法,截获数组的成员发生的变化,执行原生数组操作的同时dep通知关联的所有观察者进行响应式处理*/
[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  // cache original method
  /*将数组的原生方法缓存起来,后面要调用*/
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    // avoid leaking arguments:
    // http://jsperf.com/closure-with-arguments
    let i = arguments.length
    const args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    /*调用原生的数组方法*/
    const result = original.apply(this, args)

    /*数组新插入的元素需要重新进行observe才能响应式*/
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
        inserted = args
        break
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)

    // notify change
    /*dep通知所有注册的观察者进行响应式处理*/
    ob.dep.notify()
    return result
  })
})

从数组的原型新建一个Object.create(arrayProto)对象,通过修改此原型可以保证原生数组方法不被污染。如果当前浏览器支持__proto__这个属性的话就可以直接覆盖该属性则使数组对象具有了重写后的数组方法。如果没有该属性的浏览器,则必须通过遍历def所有需要重写的数组方法,这种方法效率较低,所以优先使用第一种。

在保证不污染不覆盖数组原生方法添加监听,主要做了两个操作,第一是通知所有注册的观察者进行响应式处理,第二是如果是添加成员的操作,需要对新成员进行observe。

但是修改了数组的原生方法以后我们还是没法像原生数组一样直接通过数组的下标或者设置length来修改数组,可以通过Vue.set以及splice方法

Watcher

Watcher是一个观察者对象。依赖收集以后Watcher对象会被保存在Deps中,数据变动的时候会由Deps通知Watcher实例,然后由Watcher实例回调cb进行视图的更新。

export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: ISet;
  newDepIds: ISet;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    /*_watchers存放订阅者实例*/
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    /*把表达式expOrFn解析成getter*/
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
   /*获得getter的值并且重新进行依赖收集*/
  get () {
    /*将自身watcher观察者实例设置给Dep.target,用以依赖收集。*/
    pushTarget(this)
    let value
    const vm = this.vm

    /*
      执行了getter操作,看似执行了渲染操作,其实是执行了依赖收集。
      在将Dep.target设置为自身观察者实例以后,执行getter操作。
      譬如说现在的的data中可能有a、b、c三个数据,getter渲染需要依赖a跟c,
      那么在执行getter的时候就会触发a跟c两个数据的getter函数,
      在getter函数中即可判断Dep.target是否存在然后完成依赖收集,
      将该观察者对象放入闭包中的Dep的subs中去。
    */
    if (this.user) {
      try {
        value = this.getter.call(vm, vm)
      } catch (e) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      }
    } else {
      value = this.getter.call(vm, vm)
    }
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    /*如果存在deep,则触发每个深层对象的依赖,追踪其变化*/
    if (this.deep) {
      /*递归每一个对象或者数组,触发它们的getter,使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系*/
      traverse(value)
    }

    /*将观察者实例从target栈中取出并设置给Dep.target*/
    popTarget()
    this.cleanupDeps()
    return value
  }

  /**
   * Add a dependency to this directive.
   */
   /*添加一个依赖关系到Deps集合中*/
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
   /*清理依赖收集*/
  cleanupDeps () {
    /*移除所有观察者对象*/
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
   /*
      调度者接口,当依赖发生改变的时候进行回调。
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      /*同步则执行run直接渲染视图*/
      this.run()
    } else {
      /*异步推送到观察者队列中,由调度者调用。*/
      queueWatcher(this)
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
   /*
      调度者工作接口,将被调度者回调。
    */
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        /*
            即便值相同,拥有Deep属性的观察者以及在对象/数组上的观察者应该被触发更新,因为它们的值可能发生改变。
        */
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        /*设置新的值*/
        this.value = value

        /*触发回调渲染视图*/
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
   /*获取观察者的值*/
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   */
   /*收集该watcher的所有deps依赖*/
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
   /*将自身从所有依赖收集订阅列表删除*/
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      /*从vm实例的观察者列表中将自身移除,由于该操作比较耗费资源,所以如果vm实例正在被销毁则跳过该步骤。*/
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

Dep

来看看Dep类。其实Dep就是一个发布者,可以订阅多个观察者,依赖收集之后Deps中会存在一个或多个Watcher对象,在数据变更的时候通知所有的Watcher。

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  /*添加一个观察者对象*/
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  /*移除一个观察者对象*/
  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  /*依赖收集,当存在Dep.target的时候添加观察者对象*/
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  /*通知所有订阅者*/
  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
/*依赖收集完需要将Dep.target设为null,防止后面重复添加依赖。*/

defineReactive

接下来是defineReactive。defineReactive的作用是通过Object.defineProperty为数据定义上getter\setter方法,进行依赖收集后闭包中的Deps会存放Watcher对象。触发setter改变数据的时候会通知Deps订阅者通知所有的Watcher观察者对象进行试图的更新。

/**
 * Define a reactive property on an Object.
 */
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: Function
) {
  /*在闭包中定义一个dep对象*/
  const dep = new Dep()

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  /*如果之前该对象已经预设了getter以及setter函数则将其取出来,新定义的getter/setter中会将其执行,保证不会覆盖之前已经定义的getter/setter。*/
  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set

  /*对象的子对象递归进行observe并返回子节点的Observer对象*/
  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {

      /*如果原本对象拥有getter方法则执行*/
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {

        /*进行依赖收集*/
        dep.depend()
        if (childOb) {

          /*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,一个是正在本身闭包中的depend,另一个是子元素的depend*/
          childOb.dep.depend()
        }
        if (Array.isArray(value)) {

          /*是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归。*/
          dependArray(value)
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {

      /*通过getter方法获取当前值,与新值进行比较,一致则不需要执行下面的操作*/
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (setter) {

        /*如果原本对象拥有setter方法则执行setter*/
        setter.call(obj, newVal)
      } else {
        val = newVal
      }

      /*新的值需要重新进行observe,保证数据响应式*/
      childOb = observe(newVal)

      /*dep对象通知所有的观察者*/
      dep.notify()
    }
  })
}

现在再来看这张图是不是更清晰了呢?

《JavaScript高级程序设计》读书笔记(一)

summary_start
《JavaScript高级程序设计》读书笔记
summary_end

1. 目录结构

  • 一、JavaScript简介
  • 二、在HTML中使用JavaScript
  • 三、基本概念
  • 四、变量、作用域和内存问题
  • 五、引用类型
  • 六、面向对象的程序设计
  • 七、匿名函数
  • 八、BOM
  • 九、客户端检测
  • 十、DOM
  • 十一、DOM2和DOM3
  • 十二、事件

由于章节较多,只记录本人认为的基础等部分。

2. 什么是JavaScript

诞生于1995年,早期设计只是为了实现表单验证。

发展史

  1. Nombas公司:C-minus-minus(Cmm)——可取代宏、与C和C++相似,后改名ScriptEase;
  2. NetSpace公司:LiveScript => JavaScript1.0,主要实现表单验证
  3. 微软:在IE 3中加入JScript
  4. 1998年,ISO/IEC正式采用ECMAScript作为标准。

实现

完整的JavaScript实现包含三个不同部分:1. 核心(ECMAScript),2. 文档对象模型(DOM),3. 浏览器对象模型(BOM)。

  • ECMAScript:提供核心语言功能;
  • DOM:提供访问和操作网页内容的方法和接口;
  • BOM:提供与浏览器交互的方法和接口。

3. 使用JavaScript

引入:在HTML页面中插入元素

按照惯例,所有script标签都应该放在页面的元素里面,但这也意味着必须在所有script加载完毕之后才呈现页面内容,这样无疑会造成很长的窗口空白期,于是可以将script放在body元素当中。

4. 基本概念

  1. 区分大小写:在ES中,所有的一切都区分大小写,这与html是有区别的,html标签与属性不区分大小写,所以一般用下划线“_”或者连字符“-”;
  2. 标识符:函数、变量、属性的名字;
  3. 注释和语句;
  4. 关键字和保留字;
  5. 变量;
  6. 数据类型;
  7. 操作符;
  8. 语句;
  9. 函数。

4.1 变量

ECMAScript中变量是松散类型,可以用来保存任何类型的数据(这点在TypeScript中有很大改变)。

定义变量时使用var操作符(ES6之后又引入了let,const)。

var:没有块的概念,ES5之前只有全局变量与函数内的局部变量,全局作用域内声明的变量所有作用域内都可以访问,函数内声明的局部变量可以在全局作用域内访问,但不能跨函数访问。

let:ES6之后引入的块的概念,使用let声明的变量只在变量所在的{}块内访问。

const:ES6之后引入的关键字,用来声明常量,声明时必须初始化,且不能改变,只能在常量所在的{}内访问。

注意:如果使用const声明了一个对象,仅代表const声明的这个对象的指针不会改变,但对象内部的值可以做改变。这点与Object对象的数据类型有关。

4.2 数据类型

ES中包含5种简单数据类型(基本数据类型)与一众复杂数据类型。

简单类型:Undefined(未定义)、Null(空)、Boolean(布尔类型)、Number(数字)、String(字符串),Symbol(后来添加)

复杂类型:Object(对象),Array(数组),Function(函数)

4.3 语句

使用一个或者多个关键字来完成给定任务。

包含:if语句,do-while语句,while语句,for语句,for-in语句,label语句,break/continue语句,with语句,switch语句。

for-in语句:一众精准的迭代语句,用来枚举对象的属性。常用语法为:

for (property in expression) statement

建议在使用for-in语法之前先检测该对象的值是否为null或者undefined

break/continue语句:用于在循环中精确的控制代码的执行。break立即退出该循环,执行循环后的语句;continue立刻退出当次循环,返回循环顶部继续执行。

4.4 函数

所有语言的核心概念,通过函数封装任意多条语句,并且可以在任何时候、任何地方调用执行。ES中使用关键字function声明函数。

基本语法:

function functionName(arg0, arg1,...,argN) {
 statements
} 

函数会在执行完return后立即停止并退出。

推荐始终让函数有返回值,或者一直没有返回值(会默认返回 undefined)

严格模式:

  • 不能把函数命名为eval或者argments;
  • 不能把参数命名为eval或者argments;
  • 不能出现同名参数。

4.4.1 参数

ECMAScript 函数不介意传递进来多少个参数,也不在乎传进来参数是什么数据类型。

因为所有传进来的参数在函数中用一个数组保存,在函数体内,也可以通过argments对象来访问这个参数数组

4.4.2 没有重载

如果定义了两个名字相同的函数,则该名字只属于后定义的函数。

5. 变量、作用域和内存问题

5.1 基本类型与引用类型的值

ES包含两种数据类型值:基本类型值(简单的数据段)和引用类型值(保存在内存中的对象)。

复制这个引用类型时,复制的是这个内存中的对象的引用;但是在对其做添加或删除属性的时候,操作的是实际的对象而不是这个对象的引用——笔者注

参数传递

ES中所有函数的参数都是按值传递的。

向函数传入基本类型值时,函数会复制该值给一个局部变量(参数),函数内部改变不影响外部变量的值。

向函数传入引用类型值时,函数会将该引用复制给一个局部变量(参数),但是在函数中改变该引用的引用类型值时,会反应在函数的外部,即函数改变了保存在内存中的对象的属性;若在函数内部对参数重新定义一个对象,则该变量的引用会改变,指向内存中新定义的对象,函数后续使用参数进行操作,将不会再体现在函数的外部

// example-1
function addTen(number) {
    number += 10;
    return number;
}
var num = 1;
var result = addTen(num);

console.log(num); // 1,无变化
console.log(result); // 11

// example-2
function setName(obj) {
    obj.name = "Jhon";
    return obj;
}
var person = new Object();
setName(person);
console.log(person.name); // "Jhon"

// example-3
function setName(obj) {
    obj.name = "Jhon";
    obj = new Object(); // 指向新的对象的引用,函数执行完后立即被销毁
    obj.name = "Mike"; // 改变新的对象的属性
}
var person = new Object();
setName(person);
console.log(person.name); // "Jhon"

5.2 执行环境和作用域

执行环境(简称“环境”),定义了变量或者函数是否有权访问其他数据,决定了他们各自的行为。

内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。这些环境之间的联系是线性、有次序的。每个环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境。

5.2.1 延长作用域链

在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。

可以用来延长作用域链的有两种情况:

  • try-catch语句的catch块;
  • with语句

5.2.2 没有块级作用域

使用var声明的变量,会自动被添加到最接近的环境中。

在ES6有新增let、const来优化这类问题。

6. 引用类型

引用类型的值(对象)是引用类型的一个实例。在 ECMAScript 中,引用类型是一种数据结构,用于将数据和功能组织在一起,也被称为“类”。

ES技术上是一门面向对象的语言,但是不具备面向对象语言所支持的接口和类等基本结构。

对象是某个特定引用类型的实例

6.1 Object类型

ES中使用最多的引用类型。创建Object实例有两种方式:

  1. new操作符后跟Object构造函数:var obj = new Object();
  2. 使用对象字面量表示法(目前最常用的方式):
var person = {
	name: "Jhon",
	age: 28
}

==使用对象也是向函数传递大量不定参数的首选方式:==

function printArgs(args) {
	if (args.name && typeof args.name === "string") {
		console.log("person's name is" + args.name);
        return args.name;
	}
	if (args.age && typeof args.age === "number") {
		console.log("person's age is" + args.age);
        return args.age;
	}
    return null;
}
printArgs({
	name: "Jhon",
	age: 28
})
printArgs({
	name: "Jhon"
})

6.2 Array类型

ES中,Array类型应该是Object之后最常用的类型。

特性:数组内部每一项都可以保存任何类型的数据;大小可以动态调整,可以根据数据变化自动增长。

数组的length属性不是只读的

数组检测:ES5中新增了Array.isArray()方法。

if (Array.isArray(value)){ 
    //对数组执行某些操作 
} 

数组常用方法如下:

数组

6.3 RegExp类型

ES通过RegExp来支持正则表达式,使用方式:var expression = / pattern / flags ;

  1. g: 表示全局(global)模式,启用表示匹配所有字符串而非匹配到第一个后立即退出
  2. i:表示不区分大小
  3. m:表示多行,即在到达一行末尾时会继续
// 匹配字符串中所有"at"的实例 
var pattern1 = /at/g;

// 匹配第一个"bat"或"cat",不区分大小写
var pattern2 = /[bc]at/i;

// 匹配所有以"at"结尾的 3 个字符的组合,不区分大小写
var pattern3 = /.at/gi;

// 匹配所有"bat"或"cat",不区分大小写
var pattern4 = /[bc]at/gi;

// 匹配第一个" [bc]at",不区分大小写
var pattern5 = /\[bc\]at/i;

// 匹配所有以"at"结尾的 3 个字符的组合,区分大小写
var pattern6 = /.at/g;

6.4 Function类型

函数实际上是对象,每个函数都是Function类型的实例。

7. 面向对象程序设计

面向对象的语言都有一个标志:都有类的概念,通过类可以创建任意多个具有相同属性和方法的对象。ES中没有类的概念,因此它的对象也与基于类的语言中的对象不同。

对象:无序属性的几个,其属性可以包含基本值、对象挥着函数。

7.1 理解对象

创建自定义对象最简单的方式就是创建一个Object实例,然后再为他添加属性和方法。

属性类型:数据属性、访问器属性

数据属性

数据属性:包含一个数据值的位置,在这个位置可以读取值和写入值。有四个描述其行为的特性。

  • [[Configerable]]:表示能否通过delete删除属性从而重新定义属性、能否修改属性的特性,或者能否把属性修改为访问器属性;默认为true。
  • [[Enumerable]]:表示能否通过for-in循环返回属性;默认为true。
  • [[Value]]:包含这个属性的数据值;默认为undefined。
  • [[Writeable]]:表示能否修改属性的值;默认为true。

访问器属性

访问器属性不包括数据值:他们包含一对getter和setter函数(不过这两个函数都不是必须的)。读取访问器属性时,调用getter方法,返回有效的值;写入访问器属性时,会调用setter函数并传入新值,setter函数负责如何处理函数。访问器属性也有四个属性描述其属性。

  • [[Configurable]]:与数据属性的Configurable属性相同。
  • [[Enumerable]]:与数据属性的Configurable属性相同。
  • [[Get]]:读取属性时调用的函数,默认为undefined。
  • [[Set]]:写入属性时调用的函数,默认为undefined。

7.2 创建对象

工厂模式

抽象出具体对象的过程。

function createPerson(name, age, job){ 
    var o = new Object(); 
    o.name = name; 
    o.age = age; 
    o.job = job; 
    o.sayName = function(){ 
        alert(this.name); 
    };     
    return o; 
} 
 
var person1 = createPerson("Nicholas", 29, "Software Engineer"); 
var person2 = createPerson("Greg", 27, "Doctor");

构造函数模式

function Person(name, age, job){ 
    this.name = name; 
    this.age = age; 
    this.job = job; 
    this.sayName = function(){ 
        alert(this.name); 
    };     
} 
 
var person1 = new Person("Nicholas", 29, "Software Engineer"); 
var person2 = new Person("Greg", 27, "Doctor");

按照惯例,构造函数都以大写字母开头。

要创建Person的新实例,必须要用new操作符,这种方式调用构造函数会经历一下四个步骤:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
  3. 执行构造函数中的代码(为这个对象添加新的属性);
  4. 返回新的对象。

任何函数,只要通过new操作符来调用,都可以作为构造函数

构造函数不使用new操作符调用,则会将结果添加到window中

Person("Greg", 27, "Doctor"); // 添加到 window

window.sayName(); //"Greg"

缺点:每个方法都要在每个实例上重新创建一遍。

解决方法:将函数定义转移到构造函数之外。

function Person(name, age, job){ 
    this.name = name; 
    this.age = age; 
    this.job = job; 
    this.sayName = sayName; 
} 
 
function sayName(){ 
    alert(this.name); 
} 
 
var person1 = new Person("Nicholas", 29, "Software Engineer"); 
var person2 = new Person("Greg", 27, "Doctor");

这个例子中,将sayName()函数的定义转移到构造函数Person外部,在构造函数内部,将sayName属性设置成等于全局的sayName()函数,即内部的sayName属性只保存了一个指向全局sayName()函数的指针(引用)。

这种方式带来的问题:1.全局作用域内定义的函数只能被某个对象调用;2.如果对象有多个方法,那么这个自定义的引用类型就没有封装意义。

原型模式

每个Object对象都有Proto属性,每个函数都有自己的prototype(原型)属性,这个属性是一个指针,指向函数对应的原型对象。使用原型对象的好处是可以让所有对象实例共享他的属性和方法。

function Person() {}
Person.prototype.name = "Mike";
Person.prototype.age = 18;
Person.prototype.sayName = function() {
    alert(this.name);
}
var person1 = new Person();
var person2 = new Person();
person1.sayName(); // Mike
person2.sayName(); // Mike
alert(person1.sayName == person2.sayName); // true

Person.prototype == person1.[[Prototype]] == person2.[[Prototype]] == Person.Prototype.constructor

可以通过对象实例访问原型中的值,但是不能重写原型的值和属性如果在实例中添加了一个属性且与原型属性同名,则会屏蔽原型中的值。

function Person() {}
Person.prototype.name = "Mike";
Person.prototype.sayName = function() {
    console.log(this.name);
}
var person1 = new Person();
var person2 = new Person();
person1.name = "Jok";
console.log(person1.name); // Jok ---- 来自实例
console.log(person2.name); // Mike ---- 来自原型

当在函数中调用person1这个实例时,首先搜索这个实例,如果没有在搜索原型。当实例中存在这个属性时,即使设置为null也不会恢复指向原型。使用delete可以完全删除这个实例属性。

person1.name = "Grey";
console.log(person1.name); // "Grey" ---- 来自实例
console.log(person1.name); // "Mike" ---- 来自原型
delete person1.name;
console.log(person1.name); // "Mike" ---- 来自原型

使用hasOwnProperty()方法可以检测一个属性是否存在于实例中。只有给定属性存在于对象实例中时才会返回true

function Person() {}
Person.prototype.name = "Mike";
var person1 = new Person();
person1.hasOwnProperty("name"); // false
person1.name = "Jok";
person1.hasOwnProperty("name"); // true
delete person1.name;
person1.hasOwnProperty("name"); // false

in操作符

in操作符有两种使用方式:单独使用、在for-in循环中使用。

单独使用时,只要能通过对象访问到给定属性即返回true,不管这个属性时在实例总还是在原型中。

var person3 = new Person();
var person4 = new Person();
person3.name = "Jok";
console.log("name" in person3); // true
console.log("name" in person4); // true

for-in循环时,返回所有可被枚举的属性,不管属性是存在于实例中还是原型中(即屏蔽了[[enumerable]]为false的属性)。

简化原型语法

function Person(){ 
} 
Person.prototype = { 
    name : "Nicholas", 
    age : 29, 
    job: "Software Engineer", 
    sayName : function () { 
        alert(this.name); 
    } 
};

组合模式

函数模式定义属性,原型模式定义方法与共享属性(目前在ES中使用最广泛,认同度最高的一宗自定义类型的方法)。

function Person(name, age, job){
	this.name = name;     
    this.age = age;     
    this.job = job;     
    this.friends = ["Shelby", "Court"]; 
}  
Person.prototype = {     
    constructor : Person,     
    sayName : function(){
        alert(this.name);     
    } 
}  
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); 
alert(person1.friends);    //"Shelby,Count,Van" 
alert(person2.friends);    //"Shelby,Count" 
alert(person1.friends === person2.friends);    //false
alert(person1.sayName === person2.sayName);    //true

动态原型模式

在构造函数中插入判断条件,只有在被检测方法不存在时,才会创建新的属性或者方法。

寄生构造函数模式

通常是在前述几种情况都不适用的情况下,可以使用该模式。

基本**是创建一个函数,但是该函数的作用仅仅是封装创建对象的代码。

function Person(name, age, job){ 
    var o = new Object(); 
    o.name = name;
    o.sayName = function(){ 
        alert(this.name); 
    };     
    return o; 
} 
 
var friend = new Person("Nicholas", 29, "Software Engineer"); 
friend.sayName();  //"Nicholas" 

这个模式可以在特殊情况下用来为对象创建构造函数。

function SpecialArray(){ 
    //创建数组 
    var values = new Array(); 
    //添加值 
    values.push.apply(values, arguments); 
    //添加方法 
    values.toPipedString = function(){ 
        return this.join("|"); 
    }; 
    //返回数组 
    return values; 
} 
 
var colors = new SpecialArray("red", "blue", "green"); 
alert(colors.toPipedString()); //"red|blue|green"

稳妥构造函数模式

稳妥对象:没有公共属性,其方法也不能引用this的对象。

稳妥对象适合用在一些安全的环境中(禁止使用this和new),或者在放在数据被其他应用程序改动时使用。

7.3 继承

原型链

ES中描述了原型链的概念,并将原型链作为实现继承的主要方法。

基本**:利用原型让一个引用类型继承另一个引用类型的属性和方法。

每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而实例都好汉一个指向原型对象内部的指针。

function SuperType(){ 
    this.property = true; 
} 
SuperType.prototype.getSuperValue = function(){ 
    return this.property; 
}; 
 
function SubType(){ 
    this.subproperty = false; 
} 
 
//继承了 SuperType 
SubType.prototype = new SuperType(); 
 
SubType.prototype.getSubValue = function (){ 
    return this.subproperty; 
}; 
 
var instance = new SubType(); 
alert(instance.getSuperValue());      //true 

该代码定义两个类型:SuperType和SubType。每个类型对应一个属性和方法。

SubType继承SuperType,该继承通过创建SuperType实例,并将实例赋值给SubType.prototype实现。本质是重写原型对象,替换厂一个新的类型的实例。

即本来存在于SuperType的实例的所有方法和属性,也存在于SubType.prototype中了。

  • 别忘记默认的原型

所有函数的默认原型都是Object实例,因此默认原型中都会包含一个内部指针指向Object.prototype。

  • 确认原型和实例的关系

第一种方式是使用instanceof操作符,第二种是使用isPrototypeOf()方法。

alert(instance instanceof Object);         //true 
alert(instance instanceof SuperType);      //true 
alert(instance instanceof SubType);        //true 

alert(Object.prototype.isPrototypeOf(instance));         //true 
alert(SuperType.prototype.isPrototypeOf(instance));      //true 
alert(SubType.prototype.isPrototypeOf(instance));        //true 

由于原型链的关系,我们可以说instance是Object,SuperType,SubType中任何一个的类型的实例。

  • 谨慎地定义方法

子类型有时候需要重写超类型中的某个方法,或者需要添加超类型中不存在的某个方法。但不管怎样,给原型添加方法的代码一定要放在替换原型的语句之后

  • 原型链的问题

构造函数定义了一个引用类型值属性时,通过原型链继承的新类型创建的实例会共享这一个引用类型的值;在创建子类型的实例时,不能向超类型的构造函数中传递参数。

借用构造函数

为解决原型中包含引用类型值所带来的问题(又叫伪造对象或者经典继承)。

基本**:在子类型构造函数内部调用超类型构造函数,可以通过call()和apply()方法。

function SuperType(){ 
    this.colors = ["red", "blue", "green"]; 
} 
 
function SubType(){   
    //继承了 继承了 SuperType 
    SuperType.call(this); 
} 
 
var instance1 = new SubType(); 
instance1.colors.push("black"); 
alert(instance1.colors);    //"red,blue,green,black" 
 
var instance2 = new SubType(); 
alert(instance2.colors);    //"red,blue,green" 
  1. 传递参数

借用沟槽函数可以在子类型构造函数中向超类型构造函数传递参数。

function SuperType(name){ 
    this.name = name; 
} 
 
function SubType(){   
    //继承了 SuperType,同时还传递了参数 
    SuperType.call(this, "Nicholas"); 
     
    //实例属性 
    this.age = 29; 
} 
 
var instance = new SubType(); 
alert(instance.name);    //"Nicholas"; 
alert(instance.age);     //29
  1. 问题

方法都在构造函数中定义,无法实现函数复用,而且超类型的原型中定义的方法对子类型也不可见。

组合继承

也叫伪经典继承,将原型链和借用构造函数的技术组合到一起。

ES中最常用的继承模式。

其他继承

  1. 原型式继承:在函数内部创建一个新的临时性构造函数,将传入参数作为内部构造函数的原型,最后返回这个临时函数的新类型。本质对传入对象执行了一次浅拷贝。
  2. 寄生式继承
  3. 寄生组合式继承

About

summary_end

教育经历

school:重庆邮电大学
professional: 网络工程
record: 本科
time: 2013.9 — 2017.06

个人优势

技术栈:Vue + Typescript + echarts + webpack + AMap + HTML/CSS

工作经历

company: 中冶赛迪重庆信息技术有限公司
job: web前端
time: 2017.07 — 至今
mainContent:

  1. 参与产品功能设计,负责业务流程与事件模块开发工作;
  2. 参与搭建Vue前端开发框架,制定小组内前端开发标准,搭建yapi接口管理系统;
  3. 基于高德地图开发设计显示用的大屏显示系统,实现图表、地图样式等自定义配置,负责整个组件的整体设计,制定与后端的接口标准,负责前端页面开发。

主要项目

1. 公司自定义组件

role: 前端开发
time: 2019.08 — 至今
projectInfo:
mainContent:

  1. 主要为提供给用户一个可以自定义的大屏可视化系统,其中我主要负责基于高德地图的地图元素组件。
    technology: Vue, ES6+,echarts,AMap.js,webpack

2. 智博会消防安保系统

role: 前端开发
time: 2019.05 — 2019.08
projectInfo:

  1. 后台管理系统:对基础的人员、车辆、设备、场所、案件等数据的管理;
  2. 大屏可视化指挥系统:基于地图对各类数据实时显示、紧急事件提醒与指派等;
  3. 移动端:接受事件指派、案件集中展示、处理及结果反馈等。
    mainContent:
  4. 基于高德地图api完成实时路况、事件定位及路线规划、安保力量分布情况等功能;
  5. 完成工作流流转过程、各种事件对应处理模板和方案等后台系统功能对应的web界面开发。
    technology: Vue, ES6+,echarts,AMap.js

3. 某互联网学院信息化建设

role: 前端开发
time: 2017.10 — 2018.05
projectInfo:

  1. 学院官方网站、学院后勤管理平台;
  2. 招商及入驻企业管理、学院论坛、电子图书馆;
  3. 信息化大屏等
    mainContent:
  4. 负责教学云平台及信息化大屏的开发设计工作
    technology: jQuery、Vue、ajax、echarts

1. 集团内部智能化餐饮系统

role: 开发助理
time: 2017.07 — 2017.10
projectInfo:

  1. 主要为实现员工消费的自动结算、每日商品预告、采购、库存管控等功能。
    mainContent:
    technology: spring MVC,ajax,jQuery,sql server等。

Vue项目中使用高德地图AMap

[pixiv: 20190520]'http://ww1.sinaimg.cn/large/0067sbCSly1g4koqx73w2j30p00ggt98.jpg'

summary_start
近期公司的项目使用到了高德地图api,由于饿了么团队封装的高德地图组件使用起来不是很方便,而且在使用前不仅需要学习vue-amap的文档,也需要学习高德地图的官方文档,使用起来比较麻烦,所以我觉得在项目中直接使用更加方便吧
summary_end

一、引入高德地图js api

第一种方式:直接在index.html页面中引入。

首先,在html中引入api;

    <script src="https://webapi.amap.com/maps?v=1.4.13&key='你自己申请的key'"></script>
    // 这种方式必须在head中引入

第二种方式:在vue.config.js中采用cdn方式引入,这种方式适用于采用打包优化。

    chainWebpack: function(config) {
        config.plugin("html").tap(args => {
            const cdn ={
                css: [],
                js: [
                    "https://webapi.amap.com/maps?v=1.4.13&key='你自己申请的key'",
                    "//webapi.amap.com/ui/1.0/main.js"
                ]
            }
            // 判断环境
            if (process.env.NODE_ENV === "production") {
                args[0].cdn = cdn.build;
            }
            if (process.env.NODE_ENV === "development") {
                args[0].cdn = cdn.dev;
            }
            return args;
        })
    }

第三种方式:异步引入

在项目主目录下新建initmap.js,异步引入高德地图api。

export default function loadMap(key, v = "1.4.14") {
  return new Promise(function(resolve, reject) {
    if (typeof AMap !== "undefined") {
      // eslint-disable-next-line no-undef
      resolve(AMap);
      return true;
    }
    window.onCallback = function() {
      // eslint-disable-next-line no-undef"
      resolve(AMap);
    };
    let script = document.createElement("script");
    script.type = "text/javascript";
    script.src = `https://webapi.amap.com/maps?v=${v}&key=${key}&callback=onCallback`;
    script.onerror = reject;
    document.head.appendChild(script);
  });
}

二、使用

<template>
  <div id="a-map" class="a-map">
  </div>
</template>
<script lang="ts">
import {Component, Vue} from "vue-property-decorator"
const AMap: any = (window as any).AMap;
@Component
export default class Map extends Vue{
  map: any;
  initMap() {
    this.map = new AMap.Map("a-map", {
      center: [106, 29],
  }
}
</script>
<style></style>

这种方式适用于直接引入api的情况,如果采用异步引入,则需要在vue组件内修改initMap方法

import loadMap from "../../map/load_map";
...
initMap() {
    loadMap(this.key).then(AMap => {
          this.map = new AMap.Map({
            center: this.center,
            zoom: this.zoom
          });
        });
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.