├── .vscode
├── settings.json
└── launch.json
├── codes
├── snabbdom
│ ├── .gitignore
│ ├── src
│ │ ├── utils.js
│ │ ├── export.js
│ │ ├── modules
│ │ │ ├── class.js
│ │ │ ├── props.js
│ │ │ └── style.js
│ │ ├── h.js
│ │ ├── vnode.js
│ │ └── domApi.js
│ ├── package.json
│ ├── demo
│ │ ├── index.ejs
│ │ └── app.js
│ ├── README.md
│ ├── webpack.config.js
│ └── LICENSE
├── minipack
│ ├── .gitignore
│ ├── demo
│ │ ├── msg.js
│ │ ├── entry.js
│ │ └── hi.js
│ ├── README.md
│ ├── package.json
│ ├── LICENSE
│ └── src
│ │ └── index.js
├── vue
│ ├── .gitignore
│ ├── src
│ │ ├── core
│ │ │ ├── components
│ │ │ │ ├── index.js
│ │ │ │ └── keep-alive.js
│ │ │ ├── vdom
│ │ │ │ ├── helpers
│ │ │ │ │ ├── is-async-placeholder.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── get-first-component-child.js
│ │ │ │ │ ├── merge-hook.js
│ │ │ │ │ ├── extract-props.js
│ │ │ │ │ ├── update-listeners.js
│ │ │ │ │ ├── normalize-children.js
│ │ │ │ │ └── resolve-async-component.js
│ │ │ │ ├── modules
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── ref.js
│ │ │ │ │ └── directives.js
│ │ │ │ ├── vnode.js
│ │ │ │ ├── create-functional-component.js
│ │ │ │ ├── create-element.js
│ │ │ │ └── create-component.js
│ │ │ ├── global-api
│ │ │ │ ├── mixin.js
│ │ │ │ ├── use.js
│ │ │ │ ├── assets.js
│ │ │ │ ├── index.js
│ │ │ │ └── extend.js
│ │ │ ├── util
│ │ │ │ ├── index.js
│ │ │ │ ├── perf.js
│ │ │ │ ├── lang.js
│ │ │ │ ├── error.js
│ │ │ │ ├── env.js
│ │ │ │ ├── debug.js
│ │ │ │ ├── next-tick.js
│ │ │ │ └── props.js
│ │ │ ├── instance
│ │ │ │ ├── render-helpers
│ │ │ │ │ ├── resolve-filter.js
│ │ │ │ │ ├── bind-object-listeners.js
│ │ │ │ │ ├── render-list.js
│ │ │ │ │ ├── check-keycodes.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── render-slot.js
│ │ │ │ │ ├── bind-object-props.js
│ │ │ │ │ ├── render-static.js
│ │ │ │ │ └── resolve-slots.js
│ │ │ │ ├── index.js
│ │ │ │ ├── inject.js
│ │ │ │ ├── proxy.js
│ │ │ │ ├── events.js
│ │ │ │ ├── render.js
│ │ │ │ └── init.js
│ │ │ ├── index.js
│ │ │ ├── observer
│ │ │ │ ├── array.js
│ │ │ │ ├── traverse.js
│ │ │ │ ├── dep.js
│ │ │ │ ├── scheduler.js
│ │ │ │ ├── watcher.js
│ │ │ │ ├── index.js
│ │ │ │ └── readme.md
│ │ │ └── config.js
│ │ └── shared
│ │ │ ├── constants.js
│ │ │ └── util.js
│ ├── .editorconfig
│ ├── .babelrc
│ ├── package.json
│ └── __test_observer.js
└── didact
│ ├── .gitignore
│ ├── test
│ ├── _browser-mock.js
│ ├── 02.re-render-element.test.js
│ ├── 03.reconciliation.test.js
│ ├── 05.set-state.test.js
│ ├── 01.render-jsx-dom-elements.test.js
│ ├── 04.components.test.js
│ └── 00.render-dom-elements.test.js
│ ├── src
│ ├── index.ts
│ ├── element.ts
│ ├── component.ts
│ ├── interface.ts
│ └── dom-utils.ts
│ ├── .babelrc
│ ├── tslint.json
│ ├── package.json
│ ├── LICENSE
│ └── tsconfig.json
├── examples
├── webpack-demo
│ ├── README.md
│ ├── .gitignore
│ ├── src
│ │ ├── js
│ │ │ ├── info.js
│ │ │ ├── constant.js
│ │ │ └── title.js
│ │ ├── images
│ │ │ ├── favicon.png
│ │ │ └── logo.svg
│ │ ├── styles
│ │ │ └── index.scss
│ │ ├── template.html
│ │ └── index.js
│ ├── .babelrc.json
│ ├── .prettierrc.json
│ ├── postcss.config.js
│ ├── config
│ │ ├── paths.js
│ │ ├── webpack.dev.js
│ │ ├── webpack.prod.js
│ │ └── webpack.common.js
│ ├── LICENSE
│ ├── .eslintrc.json
│ └── package.json
└── README.md
├── .gitignore
├── README.md
├── LICENSE.md
└── sites_and_articles.md
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | }
--------------------------------------------------------------------------------
/codes/snabbdom/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
--------------------------------------------------------------------------------
/examples/webpack-demo/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/codes/minipack/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | > examples for blog.
--------------------------------------------------------------------------------
/codes/vue/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | package-lock.json
3 |
--------------------------------------------------------------------------------
/codes/minipack/demo/msg.js:
--------------------------------------------------------------------------------
1 | export default 'Hello';
2 |
--------------------------------------------------------------------------------
/codes/didact/.gitignore:
--------------------------------------------------------------------------------
1 | lib/
2 | node_modules/
3 | package-lock.json
--------------------------------------------------------------------------------
/examples/webpack-demo/.gitignore:
--------------------------------------------------------------------------------
1 | **/.DS_Store
2 | /node_modules
3 | /dist
--------------------------------------------------------------------------------
/codes/minipack/demo/entry.js:
--------------------------------------------------------------------------------
1 | import sayHi from './hi';
2 |
3 | sayHi('Jack');
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | .DS_Store
4 |
5 | # fecs config
6 | .fecsrc
7 | .fecsignore
--------------------------------------------------------------------------------
/codes/minipack/README.md:
--------------------------------------------------------------------------------
1 | [Ronen Amiel: **Build your own webpack**](https://www.youtube.com/watch?v=Gc9-7PBqOC8) [39:38]
--------------------------------------------------------------------------------
/codes/vue/src/core/components/index.js:
--------------------------------------------------------------------------------
1 | import KeepAlive from './keep-alive'
2 |
3 | export default {
4 | KeepAlive
5 | }
6 |
--------------------------------------------------------------------------------
/examples/webpack-demo/src/js/info.js:
--------------------------------------------------------------------------------
1 | /* eslint import/prefer-default-export: "off" */
2 | export const text = '2021/02/01'
3 |
--------------------------------------------------------------------------------
/examples/webpack-demo/src/js/constant.js:
--------------------------------------------------------------------------------
1 | /* eslint import/prefer-default-export: "off" */
2 | export const author = 'Creeper'
3 |
--------------------------------------------------------------------------------
/examples/webpack-demo/src/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/creeperyang/blog/HEAD/examples/webpack-demo/src/images/favicon.png
--------------------------------------------------------------------------------
/examples/webpack-demo/.babelrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env"],
3 | "plugins": ["@babel/plugin-proposal-class-properties"]
4 | }
5 |
--------------------------------------------------------------------------------
/codes/minipack/demo/hi.js:
--------------------------------------------------------------------------------
1 | import message from './msg';
2 |
3 | export default function sayHi(who) {
4 | console.log(`> ${message} ${who}!`);
5 | }
6 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/helpers/is-async-placeholder.js:
--------------------------------------------------------------------------------
1 | export function isAsyncPlaceholder(node) {
2 | return node.isComment && node.asyncFactory
3 | }
4 |
--------------------------------------------------------------------------------
/examples/webpack-demo/src/js/title.js:
--------------------------------------------------------------------------------
1 | /* eslint import/prefer-default-export: "off" */
2 | export const getTitle = () => {
3 | return '柴门闻犬吠,风雪夜归人。'
4 | }
5 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/modules/index.js:
--------------------------------------------------------------------------------
1 | import directives from './directives'
2 | import ref from './ref'
3 |
4 | export default [
5 | ref,
6 | directives
7 | ]
8 |
--------------------------------------------------------------------------------
/examples/webpack-demo/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "singleQuote": true,
4 | "tabWidth": 2,
5 | "printWidth": 80,
6 | "semi": false
7 | }
8 |
--------------------------------------------------------------------------------
/examples/webpack-demo/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | 'postcss-preset-env': {
4 | browsers: 'last 2 versions',
5 | },
6 | },
7 | }
8 |
--------------------------------------------------------------------------------
/codes/vue/src/core/global-api/mixin.js:
--------------------------------------------------------------------------------
1 | import {
2 | mergeOptions
3 | } from '../util/index'
4 |
5 | export function initMixin(Vue) {
6 | Vue.mixin = function (mixin) {
7 | this.options = mergeOptions(this.options, mixin)
8 | return this
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/codes/didact/test/_browser-mock.js:
--------------------------------------------------------------------------------
1 | import browserEnv from 'browser-env';
2 |
3 | browserEnv(['document']);
4 |
5 | const deadline = {
6 | timeRemaining: () => 1000,
7 | };
8 |
9 | global.requestIdleCallback = function requestIdleCallback(fn) {
10 | fn(deadline);
11 | };
12 |
--------------------------------------------------------------------------------
/codes/vue/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | insert_final_newline = false
13 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/codes/didact/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Component } from './component';
2 | import { createElement } from './element';
3 | import { render } from './reconciler';
4 |
5 | export default {
6 | createElement,
7 | Component,
8 | render,
9 | };
10 |
11 | export { createElement, Component, render };
12 |
--------------------------------------------------------------------------------
/codes/vue/src/core/util/index.js:
--------------------------------------------------------------------------------
1 | export * from '../../shared/util'
2 | export * from './lang'
3 | export * from './env'
4 | export * from './options'
5 | export * from './debug'
6 | export * from './props'
7 | export * from './error'
8 | export * from './next-tick'
9 | export { defineReactive } from '../observer/index'
10 |
--------------------------------------------------------------------------------
/examples/webpack-demo/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
6 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
7 | text-align: center;
8 | background-color: #000;
9 | color: #fff;
10 | }
11 |
--------------------------------------------------------------------------------
/codes/didact/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "@babel/plugin-transform-react-jsx"
4 | ],
5 | "presets": [
6 | [
7 | "@babel/preset-env",
8 | {
9 | "targets": {
10 | "node": "current"
11 | }
12 | }
13 | ]
14 | ]
15 | }
--------------------------------------------------------------------------------
/codes/snabbdom/src/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 一些工具函数
3 | */
4 |
5 | export const isArray = Array.isArray
6 |
7 | export const isPrimitive = val => {
8 | const type = typeof val
9 | return type === 'number' || type === 'string'
10 | }
11 |
12 | export const flattenArray = array => {
13 | return Array.prototype.concat.apply([], array)
14 | }
15 |
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/render-helpers/resolve-filter.js:
--------------------------------------------------------------------------------
1 | /* */
2 |
3 | import { identity, resolveAsset } from 'core/util/index'
4 |
5 | /**
6 | * Runtime helper for resolving filters
7 | */
8 | export function resolveFilter (id ) {
9 | return resolveAsset(this.$options, 'filters', id, true) || identity
10 | }
11 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/helpers/index.js:
--------------------------------------------------------------------------------
1 | /* */
2 |
3 | export * from './merge-hook'
4 | export * from './extract-props'
5 | export * from './update-listeners'
6 | export * from './normalize-children'
7 | export * from './resolve-async-component'
8 | export * from './get-first-component-child'
9 | export * from './is-async-placeholder'
10 |
--------------------------------------------------------------------------------
/examples/webpack-demo/config/paths.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | // Source files
5 | src: path.resolve(__dirname, '../src'),
6 |
7 | // Production build files
8 | build: path.resolve(__dirname, '../dist'),
9 |
10 | // Static files that get copied to build folder
11 | public: path.resolve(__dirname, '../public'),
12 | }
13 |
--------------------------------------------------------------------------------
/codes/vue/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/env",
5 | {
6 | "modules": "commonjs",
7 | "targets": {
8 | "node": "current"
9 | }
10 | }
11 | ],
12 | ["@babel/preset-stage-2", {
13 | "decoratorsLegacy": true
14 | }]
15 | ],
16 | "plugins": [
17 | "@babel/plugin-transform-runtime"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/codes/snabbdom/src/export.js:
--------------------------------------------------------------------------------
1 | import init from './index'
2 | import h from './h'
3 | import propsModule from './modules/props'
4 | import styleModule from './modules/style'
5 | import classModule from './modules/class'
6 |
7 | const patch = init([
8 | classModule,
9 | propsModule,
10 | styleModule
11 | ])
12 |
13 | export const snabbdomBuddle = { patch, h }
14 | export default snabbdomBuddle
--------------------------------------------------------------------------------
/examples/webpack-demo/src/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "测试 vue-observer",
11 | "program": "${workspaceFolder}/codes/vue/__test_observer.js"
12 | }
13 | ]
14 | }
--------------------------------------------------------------------------------
/codes/vue/src/shared/constants.js:
--------------------------------------------------------------------------------
1 | export const SSR_ATTR = 'data-server-rendered'
2 |
3 | export const ASSET_TYPES = [
4 | 'component',
5 | 'directive',
6 | 'filter'
7 | ]
8 |
9 | export const LIFECYCLE_HOOKS = [
10 | 'beforeCreate',
11 | 'created',
12 | 'beforeMount',
13 | 'mounted',
14 | 'beforeUpdate',
15 | 'updated',
16 | 'beforeDestroy',
17 | 'destroyed',
18 | 'activated',
19 | 'deactivated',
20 | 'errorCaptured'
21 | ]
22 |
--------------------------------------------------------------------------------
/codes/minipack/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "minipack",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "start": "node src/index.js"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "MIT",
12 | "dependencies": {
13 | "@babel/core": "^7.1.2",
14 | "@babel/parser": "^7.1.3",
15 | "@babel/preset-env": "^7.1.0",
16 | "@babel/traverse": "^7.1.4"
17 | },
18 | "devDependencies": {
19 |
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/webpack-demo/src/images/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/helpers/get-first-component-child.js:
--------------------------------------------------------------------------------
1 | import {
2 | isDef
3 | } from '../../../shared/util'
4 | import {
5 | isAsyncPlaceholder
6 | } from './is-async-placeholder'
7 |
8 | export function getFirstComponentChild(children) {
9 | if (Array.isArray(children)) {
10 | for (let i = 0; i < children.length; i++) {
11 | const c = children[i]
12 | if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
13 | return c
14 | }
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/codes/snabbdom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "snabbdom-t",
3 | "version": "0.6.7",
4 | "description": "deep into snabbdom",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "build": "webpack --display-modules --display-optimization-bailout",
8 | "start": "webpack-dev-server"
9 | },
10 | "keywords": [],
11 | "author": "creeperyang",
12 | "license": "MIT",
13 | "devDependencies": {
14 | "html-webpack-plugin": "^2.28.0",
15 | "webpack": "^3.10.0",
16 | "webpack-dev-server": "^2.9.7"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/codes/snabbdom/demo/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%= htmlWebpackPlugin.options.title %>
6 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/codes/snabbdom/README.md:
--------------------------------------------------------------------------------
1 | # snabbdom 源码解析
2 |
3 | snabbdom 的 ES6 版本(有细微不同点),核心代码有详细注释,主要用于理解 snabbdom 。
4 |
5 | 代码结构:
6 |
7 | ```bash
8 | src
9 | ├── domApi.js # dom api,主要是各种 DOM 操作的包装,快速浏览即可。
10 | ├── export.js # export,决定暴露什么接口给调用者,可忽略。
11 | ├── h.js # `h()`帮助函数,很简单。
12 | ├── index.js # 核心代码,Virtual DOM 的 diff 实现,从 Virtual DOM 构建 DOM 等等。
13 | ├── modules # 各个模块,主要负责属性处理。
14 | │ ├── class.js
15 | │ ├── props.js
16 | │ └── style.js
17 | ├── utils.js # util 函数。
18 | └── vnode.js # vnode 定义和一些相关函数。
19 | ```
20 |
21 | 看 snabbdom 的实际例子:
22 |
23 | ```bash
24 | npm i && npm run start
25 | ```
--------------------------------------------------------------------------------
/codes/vue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-fake",
3 | "version": "1.0.0",
4 | "description": "vue insight",
5 | "main": "src/core/index.js",
6 | "scripts": {
7 | "build": "rm -rf dist && babel src --out-dir dist"
8 | },
9 | "license": "MIT",
10 | "devDependencies": {
11 | "@babel/cli": "^7.0.0-beta.51",
12 | "@babel/core": "^7.0.0-beta.51",
13 | "@babel/plugin-transform-runtime": "^7.0.0-beta.51",
14 | "@babel/preset-env": "^7.0.0-beta.51",
15 | "@babel/preset-stage-2": "^7.0.0-beta.51"
16 | },
17 | "dependencies": {
18 | "@babel/runtime": "^7.0.0-beta.51"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/codes/didact/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 | "jsRules": {},
7 | "rules": {
8 | "object-literal-sort-keys": false,
9 | "quotemark": [
10 | true,
11 | "single"
12 | ],
13 | "one-line": [
14 | true,
15 | "check-open-brace",
16 | "check-whitespace"
17 | ],
18 | "no-bitwise": false,
19 | "object-literal-key-quotes": [
20 | true,
21 | "as-needed"
22 | ]
23 | },
24 | "rulesDirectory": []
25 | }
--------------------------------------------------------------------------------
/codes/vue/src/core/util/perf.js:
--------------------------------------------------------------------------------
1 | import { inBrowser } from './env'
2 |
3 | export let mark
4 | export let measure
5 |
6 | if (process.env.NODE_ENV !== 'production') {
7 | const perf = inBrowser && window.performance
8 | /* istanbul ignore if */
9 | if (
10 | perf &&
11 | perf.mark &&
12 | perf.measure &&
13 | perf.clearMarks &&
14 | perf.clearMeasures
15 | ) {
16 | mark = tag => perf.mark(tag)
17 | measure = (name, startTag, endTag) => {
18 | perf.measure(name, startTag, endTag)
19 | perf.clearMarks(startTag)
20 | perf.clearMarks(endTag)
21 | perf.clearMeasures(name)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/codes/vue/__test_observer.js:
--------------------------------------------------------------------------------
1 | const { observe } = require('./dist/core/observer')
2 | const Watcher = require('./dist/core/observer/watcher').default
3 |
4 | function createWatcher(data, expOrFn, cb, options) {
5 | const vm = {
6 | data: data || {},
7 | _watchers: []
8 | }
9 | observe(vm.data, true)
10 | return new Watcher(vm, expOrFn, cb, options)
11 | }
12 |
13 | const raw = {
14 | s: 'hi',
15 | n: 100,
16 | o: {x: 1, arr: [1, 2]},
17 | arr: [8, 9]
18 | }
19 |
20 | const w = createWatcher(raw, function () {
21 | return this.data.arr
22 | }, (a, b) => {
23 | console.log('--->', a, b)
24 | }, {
25 | deep: false,
26 | sync: true
27 | })
28 |
29 | raw.arr.push(10)
30 |
--------------------------------------------------------------------------------
/codes/didact/src/element.ts:
--------------------------------------------------------------------------------
1 | import { IProps } from './interface';
2 |
3 | export const TEXT_ELEMENT = 'TEXT ELEMENT';
4 |
5 | export function createElement(type: string, config: object, ...args: any[]) {
6 | const props: IProps = Object.assign({}, config);
7 | const hasChildren = args.length > 0;
8 | const rawChildren = hasChildren ? [].concat(...args) : [];
9 | props.children = rawChildren
10 | .filter((c) => c != null && c !== false)
11 | .map((c: any) => c instanceof Object ? c : createTextElement(c));
12 | return { type, props };
13 | }
14 |
15 | function createTextElement(value: string) {
16 | return createElement(TEXT_ELEMENT, { nodeValue: value });
17 | }
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 所有文章
9 |
10 |
11 |
12 |
13 | 关于订阅
14 |
15 | 喜欢请点右上角 `star`。订阅的话,请 `watch` 按钮。
16 |
17 | 转载注意事项
18 |
19 | 除注明外,所有文章均采用Creative Commons BY-NC-ND 4.0(自由转载-保持署名-非商用-禁止演绎)协议发布。
20 |
21 |
--------------------------------------------------------------------------------
/codes/snabbdom/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { join } = require('path')
2 | const HtmlWebpackPlugin = require('html-webpack-plugin')
3 |
4 | module.exports = [
5 | {
6 | entry: './src/export.js',
7 | output: {
8 | libraryTarget: 'umd',
9 | libraryExport: 'default',
10 | library: 'snabbdom',
11 | filename: 'snabbdom.js',
12 | path: join(__dirname, 'dist')
13 | }
14 | },
15 | {
16 | entry: './demo/app.js',
17 | output: {
18 | filename: 'bundle.js',
19 | path: join(__dirname, 'dist')
20 | },
21 | plugins: [
22 | new HtmlWebpackPlugin({
23 | title: 'snabbdom',
24 | template: 'demo/index.ejs'
25 | })
26 | ]
27 | }
28 | ]
29 |
--------------------------------------------------------------------------------
/codes/vue/src/core/global-api/use.js:
--------------------------------------------------------------------------------
1 | import {
2 | toArray
3 | } from '../util/index'
4 |
5 | export function initUse(Vue) {
6 | Vue.use = function (plugin) {
7 | const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
8 | if (installedPlugins.indexOf(plugin) > -1) {
9 | return this
10 | }
11 |
12 | // additional parameters
13 | const args = toArray(arguments, 1)
14 | args.unshift(this)
15 | if (typeof plugin.install === 'function') {
16 | plugin.install.apply(plugin, args)
17 | } else if (typeof plugin === 'function') {
18 | plugin.apply(null, args)
19 | }
20 | installedPlugins.push(plugin)
21 | return this
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/codes/didact/test/02.re-render-element.test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import './_browser-mock';
3 | /** @jsx createElement */
4 | import { render, createElement } from '../lib';
5 |
6 | test.beforeEach(t => {
7 | let root = document.getElementById('root');
8 | if (!root) {
9 | root = document.createElement('div');
10 | root.id = 'root';
11 | document.body.appendChild(root);
12 | }
13 | t.context.root = root;
14 | });
15 |
16 | test('render jsx div', t => {
17 | const root = t.context.root;
18 | const element = Foo
;
19 | render(element, root);
20 | t.is(root.innerHTML, 'Foo
');
21 | render(element, root);
22 | t.is(root.innerHTML, 'Foo
');
23 | });
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | initMixin
3 | } from './init'
4 | import {
5 | stateMixin
6 | } from './state'
7 | import {
8 | renderMixin
9 | } from './render'
10 | import {
11 | eventsMixin
12 | } from './events'
13 | import {
14 | lifecycleMixin
15 | } from './lifecycle'
16 | import {
17 | warn
18 | } from '../util/index'
19 |
20 | function Vue(options) {
21 | if (process.env.NODE_ENV !== 'production' &&
22 | !(this instanceof Vue)
23 | ) {
24 | warn('Vue is a constructor and should be called with the `new` keyword')
25 | }
26 | this._init(options)
27 | }
28 |
29 | initMixin(Vue)
30 | stateMixin(Vue)
31 | eventsMixin(Vue)
32 | lifecycleMixin(Vue)
33 | renderMixin(Vue)
34 |
35 | export default Vue
36 |
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/render-helpers/bind-object-listeners.js:
--------------------------------------------------------------------------------
1 | /* */
2 |
3 | import { warn, extend, isPlainObject } from 'core/util/index'
4 |
5 | export function bindObjectListeners (data , value ) {
6 | if (value) {
7 | if (!isPlainObject(value)) {
8 | process.env.NODE_ENV !== 'production' && warn(
9 | 'v-on without argument expects an Object value',
10 | this
11 | )
12 | } else {
13 | const on = data.on = data.on ? extend({}, data.on) : {}
14 | for (const key in value) {
15 | const existing = on[key]
16 | const ours = value[key]
17 | on[key] = existing ? [].concat(existing, ours) : ours
18 | }
19 | }
20 | }
21 | return data
22 | }
23 |
--------------------------------------------------------------------------------
/codes/vue/src/core/index.js:
--------------------------------------------------------------------------------
1 | import Vue from './instance/index'
2 | import { initGlobalAPI } from './global-api/index'
3 | import { isServerRendering } from 'core/util/env'
4 | import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
5 |
6 | initGlobalAPI(Vue)
7 |
8 | Object.defineProperty(Vue.prototype, '$isServer', {
9 | get: isServerRendering
10 | })
11 |
12 | Object.defineProperty(Vue.prototype, '$ssrContext', {
13 | get () {
14 | /* istanbul ignore next */
15 | return this.$vnode && this.$vnode.ssrContext
16 | }
17 | })
18 |
19 | // expose FunctionalRenderContext for ssr runtime helper installation
20 | Object.defineProperty(Vue, 'FunctionalRenderContext', {
21 | value: FunctionalRenderContext
22 | })
23 |
24 | Vue.version = '__VERSION__'
25 |
26 | export default Vue
27 |
--------------------------------------------------------------------------------
/examples/webpack-demo/src/index.js:
--------------------------------------------------------------------------------
1 | // Test import of a JavaScript function
2 | import { getTitle } from './js/title'
3 |
4 | // Test import of an asset
5 | import myLogo from './images/logo.svg'
6 |
7 | // Test import of styles
8 | import './styles/index.scss'
9 |
10 | const logo = document.createElement('img')
11 | logo.src = myLogo
12 |
13 | const heading = document.createElement('h1')
14 | heading.textContent = getTitle()
15 |
16 | const app = document.querySelector('#root')
17 | app.append(logo, heading)
18 |
19 | import('./js/info').then((v) => {
20 | const footer = document.createElement('footer')
21 | footer.textContent = v.text
22 | app.append(footer)
23 | })
24 |
25 | import('./js/constant').then((v) => {
26 | const div = document.createElement('div')
27 | div.textContent = v.author
28 | app.append(div)
29 | })
30 |
--------------------------------------------------------------------------------
/codes/snabbdom/src/modules/class.js:
--------------------------------------------------------------------------------
1 | /**
2 | * class 模块:支持 vnode 使用 className 来操作 html class。
3 | */
4 |
5 | import { isArray } from '../utils'
6 |
7 | function updateClassName(oldVnode, vnode) {
8 | const oldName = oldVnode.data.className
9 | const newName = vnode.data.className
10 |
11 | if (!oldName && !newName) return
12 | if (oldName === newName) return
13 |
14 | const elm = vnode.elm
15 | if (typeof newName === 'string' && newName) {
16 | elm.className = newName.toString()
17 | } else if (isArray(newName)) {
18 | elm.className = ''
19 | newName.forEach(v => {
20 | elm.classList.add(v)
21 | })
22 | } else {
23 | // 所有不合法的值或者空值,都把 className 设为 ''
24 | elm.className = ''
25 | }
26 | }
27 |
28 | export const classModule = {
29 | create: updateClassName,
30 | update: updateClassName
31 | }
32 | export default classModule
33 |
--------------------------------------------------------------------------------
/codes/vue/src/core/util/lang.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Check if a string starts with $ or _
3 | */
4 | export function isReserved(str) {
5 | const c = (str + '').charCodeAt(0)
6 | return c === 0x24 || c === 0x5F
7 | }
8 |
9 | /**
10 | * Define a property.
11 | */
12 | export function def(obj, key, val, enumerable) {
13 | Object.defineProperty(obj, key, {
14 | value: val,
15 | enumerable: !!enumerable,
16 | writable: true,
17 | configurable: true
18 | })
19 | }
20 |
21 | /**
22 | * Parse simple path.
23 | */
24 | const bailRE = /[^\w.$]/
25 | export function parsePath(path) {
26 | if (bailRE.test(path)) {
27 | return
28 | }
29 | const segments = path.split('.')
30 | return function (obj) {
31 | for (let i = 0; i < segments.length; i++) {
32 | if (!obj) return
33 | obj = obj[segments[i]]
34 | }
35 | return obj
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/codes/snabbdom/src/modules/props.js:
--------------------------------------------------------------------------------
1 | /**
2 | * props 模块:支持 vnode 使用 props 来操作其它属性。
3 | */
4 | function filterKeys(obj) {
5 | return Object.keys(obj).filter(k => {
6 | return k !== 'style' && k !== 'id' && k !== 'class'
7 | })
8 | }
9 |
10 | function updateProps(oldVnode, vnode) {
11 | let oldProps = oldVnode.data.props
12 | let props = vnode.data.props
13 | const elm = vnode.elm
14 | let key, cur, old
15 |
16 | if (!oldProps && !props) return
17 | if (oldProps === props) return
18 | oldProps = oldProps || {}
19 | props = props || {}
20 |
21 | filterKeys(oldProps).forEach(key => {
22 | if (!props[key]) {
23 | delete elm[key]
24 | }
25 | })
26 | filterKeys(props).forEach(key => {
27 | cur = props[key]
28 | old = oldProps[key]
29 | if (old !== cur && (key !== 'value' || elm[key] !== cur)) {
30 | elm[key] = cur
31 | }
32 | })
33 | }
34 |
35 | export const propsModule = {create: updateProps, update: updateProps}
36 | export default propsModule
37 |
--------------------------------------------------------------------------------
/codes/didact/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "didact",
3 | "version": "2.0.1",
4 | "description": "A didactic alternative to React.",
5 | "license": "MIT",
6 | "main": "lib/didact.umd.js",
7 | "module": "lib/didact.es.js",
8 | "files": [
9 | "lib"
10 | ],
11 | "scripts": {
12 | "build": "rm -rf lib && tsc -P tsconfig.json",
13 | "test": "npm run build && ava",
14 | "lint": "tslint src/**/*.ts -t verbose"
15 | },
16 | "keywords": [
17 | "react"
18 | ],
19 | "devDependencies": {
20 | "@babel/core": "^7.0.0-beta.42",
21 | "@babel/plugin-transform-react-jsx": "^7.0.0-beta.42",
22 | "@babel/preset-env": "^7.0.0-beta.42",
23 | "@babel/register": "^7.0.0-beta.42",
24 | "ava": "^1.0.0-beta.3",
25 | "browser-env": "^3.2.5",
26 | "tslint": "^5.9.1",
27 | "typescript": "^2.7.2"
28 | },
29 | "ava": {
30 | "require": [
31 | "@babel/register"
32 | ]
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/codes/snabbdom/src/h.js:
--------------------------------------------------------------------------------
1 | import { isArray, isPrimitive, flattenArray } from './utils'
2 | import vnode from './vnode'
3 |
4 | const hasOwnProperty = Object.prototype.hasOwnProperty
5 | const RESERVED_PROPS = {
6 | key: true,
7 | __self: true,
8 | __source: true
9 | }
10 |
11 | export default h
12 |
13 | function hasValidKey(config) {
14 | return config.key !== undefined
15 | }
16 |
17 | function h(type, config, ...children) {
18 | const props = {}
19 |
20 | let key = null
21 |
22 | // 获取 key,填充 props 对象
23 | if (config != null) {
24 | if (hasValidKey(config)) {
25 | key = '' + config.key
26 | }
27 |
28 | for (let propName in config) {
29 | if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS[propName]) {
30 | props[propName] = config[propName]
31 | }
32 | }
33 | }
34 |
35 | return vnode(
36 | type,
37 | key,
38 | props,
39 | flattenArray(children).map(c => {
40 | return isPrimitive(c) ? vnode(undefined, undefined, undefined, undefined, c) : c
41 | })
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/codes/didact/src/component.ts:
--------------------------------------------------------------------------------
1 | import { IFiber, IProps, IState, IVNode } from './interface';
2 | import { scheduleUpdate } from './reconciler';
3 |
4 | /**
5 | * @name Component
6 | * @description 组件基类,定义了构造函数和 setState
7 | */
8 | export class Component {
9 | public props: IProps;
10 | public state?: IState;
11 | // tslint:disable-next-line:variable-name
12 | public __fiber?: IFiber;
13 | constructor(props: IProps | null) {
14 | // 初始化 props 和 state
15 | this.props = props || {};
16 | }
17 | // 当子类的实例调用 setState 时,触发更新计划
18 | public setState(partialState: any) {
19 | scheduleUpdate(this, partialState);
20 | }
21 | public render(): IVNode | IVNode[] | null | undefined {
22 | throw new Error('subclass must implement render method.');
23 | }
24 | }
25 |
26 | /**
27 | * 创建组件实例
28 | * @param {Fiber} fiber 从fiber创建组件实例
29 | */
30 | export function createInstance(fiber: IFiber) {
31 | const instance: Component = new (fiber.type as any)(fiber.props);
32 | instance.__fiber = fiber;
33 | return instance;
34 | }
35 |
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/render-helpers/render-list.js:
--------------------------------------------------------------------------------
1 | /* */
2 |
3 | import { isObject, isDef } from 'core/util/index'
4 |
5 | /**
6 | * Runtime helper for rendering v-for lists.
7 | */
8 | export function renderList (
9 | val ,
10 | render
11 |
12 |
13 |
14 |
15 | ) {
16 | let ret , i, l, keys, key
17 | if (Array.isArray(val) || typeof val === 'string') {
18 | ret = new Array(val.length)
19 | for (i = 0, l = val.length; i < l; i++) {
20 | ret[i] = render(val[i], i)
21 | }
22 | } else if (typeof val === 'number') {
23 | ret = new Array(val)
24 | for (i = 0; i < val; i++) {
25 | ret[i] = render(i + 1, i)
26 | }
27 | } else if (isObject(val)) {
28 | keys = Object.keys(val)
29 | ret = new Array(keys.length)
30 | for (i = 0, l = keys.length; i < l; i++) {
31 | key = keys[i]
32 | ret[i] = render(val[key], key, i)
33 | }
34 | }
35 | if (isDef(ret)) {
36 | (ret )._isVList = true
37 | }
38 | return ret
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Creeper
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/codes/didact/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Hexacta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/codes/minipack/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Ronen Amiel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/examples/webpack-demo/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Tania Rascia
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/render-helpers/check-keycodes.js:
--------------------------------------------------------------------------------
1 | /* */
2 |
3 | import config from 'core/config'
4 | import { hyphenate } from 'shared/util'
5 |
6 | function isKeyNotMatch (expect , actual ) {
7 | if (Array.isArray(expect)) {
8 | return expect.indexOf(actual) === -1
9 | } else {
10 | return expect !== actual
11 | }
12 | }
13 |
14 | /**
15 | * Runtime helper for checking keyCodes from config.
16 | * exposed as Vue.prototype._k
17 | * passing in eventKeyName as last argument separately for backwards compat
18 | */
19 | export function checkKeyCodes (
20 | eventKeyCode ,
21 | key ,
22 | builtInKeyCode ,
23 | eventKeyName ,
24 | builtInKeyName
25 | ) {
26 | const mappedKeyCode = config.keyCodes[key] || builtInKeyCode
27 | if (builtInKeyName && eventKeyName && !config.keyCodes[key]) {
28 | return isKeyNotMatch(builtInKeyName, eventKeyName)
29 | } else if (mappedKeyCode) {
30 | return isKeyNotMatch(mappedKeyCode, eventKeyCode)
31 | } else if (eventKeyName) {
32 | return hyphenate(eventKeyName) !== key
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/codes/vue/src/core/observer/array.js:
--------------------------------------------------------------------------------
1 | /*
2 | * not type checking this file because flow doesn't play well with
3 | * dynamically accessing methods on Array prototype
4 | */
5 |
6 | import { def } from '../util/index'
7 |
8 | const arrayProto = Array.prototype
9 | export const arrayMethods = Object.create(arrayProto)
10 |
11 | const methodsToPatch = [
12 | 'push',
13 | 'pop',
14 | 'shift',
15 | 'unshift',
16 | 'splice',
17 | 'sort',
18 | 'reverse'
19 | ]
20 |
21 | /**
22 | * Intercept mutating methods and emit events
23 | */
24 | methodsToPatch.forEach(function (method) {
25 | // cache original method
26 | const original = arrayProto[method]
27 | def(arrayMethods, method, function mutator (...args) {
28 | const result = original.apply(this, args)
29 | const ob = this.__ob__
30 | let inserted
31 | switch (method) {
32 | case 'push':
33 | case 'unshift':
34 | inserted = args
35 | break
36 | case 'splice':
37 | inserted = args.slice(2)
38 | break
39 | }
40 | if (inserted) ob.observeArray(inserted)
41 | // notify change
42 | ob.dep.notify()
43 | return result
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/render-helpers/index.js:
--------------------------------------------------------------------------------
1 | /* */
2 |
3 | import { toNumber, toString, looseEqual, looseIndexOf } from 'shared/util'
4 | import { createTextVNode, createEmptyVNode } from 'core/vdom/vnode'
5 | import { renderList } from './render-list'
6 | import { renderSlot } from './render-slot'
7 | import { resolveFilter } from './resolve-filter'
8 | import { checkKeyCodes } from './check-keycodes'
9 | import { bindObjectProps } from './bind-object-props'
10 | import { renderStatic, markOnce } from './render-static'
11 | import { bindObjectListeners } from './bind-object-listeners'
12 | import { resolveScopedSlots } from './resolve-slots'
13 |
14 | export function installRenderHelpers (target ) {
15 | target._o = markOnce
16 | target._n = toNumber
17 | target._s = toString
18 | target._l = renderList
19 | target._t = renderSlot
20 | target._q = looseEqual
21 | target._i = looseIndexOf
22 | target._m = renderStatic
23 | target._f = resolveFilter
24 | target._k = checkKeyCodes
25 | target._b = bindObjectProps
26 | target._v = createTextVNode
27 | target._e = createEmptyVNode
28 | target._u = resolveScopedSlots
29 | target._g = bindObjectListeners
30 | }
31 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/helpers/merge-hook.js:
--------------------------------------------------------------------------------
1 | import VNode from '../vnode'
2 | import {
3 | createFnInvoker
4 | } from './update-listeners'
5 | import {
6 | remove,
7 | isDef,
8 | isUndef,
9 | isTrue
10 | } from '../../../shared/util'
11 |
12 | export function mergeVNodeHook(def, hookKey, hook) {
13 | if (def instanceof VNode) {
14 | def = def.data.hook || (def.data.hook = {})
15 | }
16 | let invoker
17 | const oldHook = def[hookKey]
18 |
19 | function wrappedHook() {
20 | hook.apply(this, arguments)
21 | // important: remove merged hook to ensure it's called only once
22 | // and prevent memory leak
23 | remove(invoker.fns, wrappedHook)
24 | }
25 |
26 | if (isUndef(oldHook)) {
27 | // no existing hook
28 | invoker = createFnInvoker([wrappedHook])
29 | } else {
30 | /* istanbul ignore if */
31 | if (isDef(oldHook.fns) && isTrue(oldHook.merged)) {
32 | // already a merged invoker
33 | invoker = oldHook
34 | invoker.fns.push(wrappedHook)
35 | } else {
36 | // existing plain hook
37 | invoker = createFnInvoker([oldHook, wrappedHook])
38 | }
39 | }
40 |
41 | invoker.merged = true
42 | def[hookKey] = invoker
43 | }
44 |
--------------------------------------------------------------------------------
/codes/vue/src/core/global-api/assets.js:
--------------------------------------------------------------------------------
1 | import {
2 | ASSET_TYPES
3 | } from '../../shared/constants'
4 | import {
5 | isPlainObject,
6 | validateComponentName
7 | } from '../util/index'
8 |
9 | export function initAssetRegisters(Vue) {
10 | /**
11 | * Create asset registration methods.
12 | */
13 | ASSET_TYPES.forEach(type => {
14 | Vue[type] = function (
15 | id,
16 | definition
17 | ) {
18 | if (!definition) {
19 | return this.options[type + 's'][id]
20 | } else {
21 | /* istanbul ignore if */
22 | if (process.env.NODE_ENV !== 'production' && type === 'component') {
23 | validateComponentName(id)
24 | }
25 | if (type === 'component' && isPlainObject(definition)) {
26 | definition.name = definition.name || id
27 | definition = this.options._base.extend(definition)
28 | }
29 | if (type === 'directive' && typeof definition === 'function') {
30 | definition = {
31 | bind: definition,
32 | update: definition
33 | }
34 | }
35 | this.options[type + 's'][id] = definition
36 | return definition
37 | }
38 | }
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/sites_and_articles.md:
--------------------------------------------------------------------------------
1 | 记录优秀的技术网站和文章。
2 |
3 | ### 文章
4 |
5 | 1. [通过源码解析 Node.js 中 cluster 模块的主要功能实现](https://cnodejs.org/topic/56e84480833b7c8a0492e20c) `node.js/cluster/source-code`
6 | 2. [Clearing up the Babel 6 Ecosystem](https://medium.com/@jcse/clearing-up-the-babel-6-ecosystem-c7678a314bf3#.f0xpc1nfa) `babel/babel-polyfill/babel-runtime`
7 | 3. [Node.js 启动方式:一道关于全局变量的题目引发的思考](https://xcoder.in/2015/11/26/a-js-problem-about-global/) `node.js/source-code/global variable`
8 | 4. [如何 hack Node.js 模块?](http://taobaofed.org/blog/2016/10/27/how-to-hack-nodejs-modules/) `node.js/source-code/module`
9 | 5. [Node.js定制REPL的妙用](https://cnodejs.org/topic/563735ed677332084c319d95) `node.js/repl`
10 | 6. [Node.js源码阅读笔记](https://cattail.me/tech/2014/10/16/nodejs-source-reading-note.html) `node.js/source-code`
11 | 7. [谷歌IO--V8之JavaScript性能](http://v8-io12.appspot.com/index.html)
12 | 8. [5 Easy Steps to Understanding JSON Web Tokens (JWT)](https://medium.com/vandium-software/5-easy-steps-to-understanding-json-web-tokens-jwt-1164c0adfcec) `jwt` *(简洁地介绍了jwt是什么,怎么生成,做什么。jwt:完全不负责数据的安全性,只用于校验用户的来源)*
13 |
14 | ### 网站
15 |
16 | - [alinode解读node源码:deep-into-node](https://yjhjstz.gitbooks.io/deep-into-node/)
17 | - [netflix技术博客](http://techblog.netflix.com/)
18 |
--------------------------------------------------------------------------------
/codes/didact/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
5 | "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
6 | "lib": [
7 | "es2015",
8 | "dom"
9 | ],
10 | "declaration": true, /* Generates corresponding '.d.ts' file. */
11 | "outDir": "./lib", /* Redirect output structure to the directory. */
12 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
13 | /* Strict Type-Checking Options */
14 | "strict": true, /* Enable all strict type-checking options. */
15 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
16 | /* Additional Checks */
17 | "noUnusedLocals": true, /* Report errors on unused locals. */
18 | "noUnusedParameters": true /* Report errors on unused parameters. */
19 | },
20 | "exclude": [
21 | "node_modules",
22 | "test"
23 | ]
24 | }
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/modules/ref.js:
--------------------------------------------------------------------------------
1 | /* */
2 |
3 | import { remove, isDef } from 'shared/util'
4 |
5 | export default {
6 | create (_ , vnode ) {
7 | registerRef(vnode)
8 | },
9 | update (oldVnode , vnode ) {
10 | if (oldVnode.data.ref !== vnode.data.ref) {
11 | registerRef(oldVnode, true)
12 | registerRef(vnode)
13 | }
14 | },
15 | destroy (vnode ) {
16 | registerRef(vnode, true)
17 | }
18 | }
19 |
20 | export function registerRef (vnode , isRemoval ) {
21 | const key = vnode.data.ref
22 | if (!isDef(key)) return
23 |
24 | const vm = vnode.context
25 | const ref = vnode.componentInstance || vnode.elm
26 | const refs = vm.$refs
27 | if (isRemoval) {
28 | if (Array.isArray(refs[key])) {
29 | remove(refs[key], ref)
30 | } else if (refs[key] === ref) {
31 | refs[key] = undefined
32 | }
33 | } else {
34 | if (vnode.data.refInFor) {
35 | if (!Array.isArray(refs[key])) {
36 | refs[key] = [ref]
37 | } else if (refs[key].indexOf(ref) < 0) {
38 | // $flow-disable-line
39 | refs[key].push(ref)
40 | }
41 | } else {
42 | refs[key] = ref
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/webpack-demo/config/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const { merge } = require('webpack-merge')
3 | const common = require('./webpack.common.js')
4 | const paths = require('./paths')
5 |
6 | module.exports = merge(common, {
7 | // Set the mode to development or production
8 | mode: 'development',
9 |
10 | // Control how source maps are generated
11 | devtool: 'inline-source-map',
12 |
13 | // Spin up a server for quick development
14 | devServer: {
15 | historyApiFallback: true,
16 | contentBase: paths.build,
17 | open: true,
18 | compress: true,
19 | hot: true,
20 | port: 8080,
21 | },
22 |
23 | module: {
24 | rules: [
25 | // Styles: Inject CSS into the head with source maps
26 | {
27 | test: /\.(scss|css)$/,
28 | use: [
29 | 'style-loader',
30 | {loader: 'css-loader', options: {sourceMap: true, importLoaders: 1, modules: true }},
31 | {loader: 'postcss-loader', options: {sourceMap: true}},
32 | {loader: 'sass-loader', options: {sourceMap: true}},
33 | ],
34 | },
35 | ]
36 | },
37 |
38 | plugins: [
39 | // Only update what has changed on hot reload
40 | new webpack.HotModuleReplacementPlugin(),
41 | ],
42 | })
43 |
--------------------------------------------------------------------------------
/codes/snabbdom/src/vnode.js:
--------------------------------------------------------------------------------
1 | // ⚠️ 通过 symbol 保证唯一性,用于检测是不是 vnode
2 | const VNODE_TYPE = Symbol('virtual-node')
3 |
4 | /**
5 | * 生成 vnode
6 | * @param {String} type 类型,如 'div'
7 | * @param {String} key key
8 | * @param {Object} data data,包括属性,事件等等
9 | * @param {Array} children 子 vnode
10 | * @param {String} text 文本
11 | * @param {Element} elm 对应的 dom
12 | * @return {Object} vnode
13 | */
14 | function vnode(type, key, data, children, text, elm) {
15 | const element = {
16 | __type: VNODE_TYPE,
17 | type, key, data, children, text, elm
18 | }
19 |
20 | return element
21 | }
22 |
23 | /**
24 | * 校验是不是 vnode,主要检查 __type。
25 | * @param {Object} vnode 要检查的对象
26 | * @return {Boolean} 是则 true,否则 false
27 | */
28 | function isVnode(vnode) {
29 | return vnode && vnode.__type === VNODE_TYPE
30 | }
31 |
32 | /**
33 | * 检查两个 vnode 是不是同一个:key 相同且 type 相同。
34 | * @param {Object} oldVnode 前一个 vnode
35 | * @param {Object} vnode 后一个 vnode
36 | * @return {Boolean} 是则 true,否则 false
37 | */
38 | function isSameVnode(oldVnode, vnode) {
39 | return oldVnode.key === vnode.key && oldVnode.type === vnode.type
40 | }
41 |
42 | export default vnode
43 | export {
44 | isVnode,
45 | isSameVnode,
46 | VNODE_TYPE
47 | }
48 |
--------------------------------------------------------------------------------
/codes/vue/src/core/observer/traverse.js:
--------------------------------------------------------------------------------
1 | import {
2 | _Set as Set,
3 | isObject
4 | } from '../util/index'
5 |
6 | import VNode from '../vdom/vnode'
7 |
8 | const seenObjects = new Set()
9 |
10 | /**
11 | * Recursively traverse an object to evoke all converted
12 | * getters, so that every nested property inside the object
13 | * is collected as a "deep" dependency.
14 | * 深度收集依赖, 通过 seenObjects 防止重复收集。
15 | */
16 | export function traverse(val) {
17 | _traverse(val, seenObjects)
18 | seenObjects.clear()
19 | }
20 |
21 | // 递归遍历 val,深度收集依赖
22 | function _traverse(val, seen) {
23 | let i, keys
24 | const isA = Array.isArray(val)
25 | // 如果不是数组或对象,或者是 VNode,或者frozen,则不再处理。
26 | if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
27 | return
28 | }
29 | if (val.__ob__) {
30 | const depId = val.__ob__.dep.id
31 | // 如果已经收集过,则不再重复处理了。
32 | if (seen.has(depId)) {
33 | return
34 | }
35 | seen.add(depId)
36 | }
37 |
38 | // 对数组的每个元素调用 _traverse
39 | if (isA) {
40 | i = val.length
41 | while (i--) _traverse(val[i], seen)
42 | }
43 | // 对子属性(val[keys[i]])访问,即调用 defineReactvie 定义的 getter,收集依赖
44 | else {
45 | keys = Object.keys(val)
46 | i = keys.length
47 | while (i--) _traverse(val[keys[i]], seen)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/codes/didact/test/03.reconciliation.test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import './_browser-mock';
3 | /** @jsx createElement */
4 | import { render, createElement } from '../lib';
5 |
6 | test.beforeEach(t => {
7 | let root = document.getElementById('root');
8 | if (!root) {
9 | root = document.createElement('div');
10 | root.id = 'root';
11 | document.body.appendChild(root);
12 | }
13 | t.context.root = root;
14 | });
15 |
16 | test('replace div to span', t => {
17 | const root = t.context.root;
18 | let element = Foo
;
19 | render(element, root);
20 | t.is(root.innerHTML, 'Foo
');
21 | const prevChild = root.firstElementChild;
22 | element = Foo;
23 | render(element, root);
24 | t.is(root.innerHTML, 'Foo');
25 | const nextChild = root.firstElementChild;
26 | t.not(prevChild, nextChild);
27 | });
28 |
29 | test('reuse div', t => {
30 | const root = t.context.root;
31 | let element = Foo
;
32 | render(element, root);
33 | t.is(root.innerHTML, 'Foo
');
34 | const prevChild = root.firstElementChild;
35 | element = Bar
;
36 | render(element, root);
37 | t.is(root.innerHTML, 'Bar
');
38 | const nextChild = root.firstElementChild;
39 | t.is(prevChild, nextChild);
40 | });
41 |
--------------------------------------------------------------------------------
/codes/didact/test/05.set-state.test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import './_browser-mock';
3 | /** @jsx createElement */
4 | import { render, createElement, Component } from '../lib';
5 |
6 | test.beforeEach(t => {
7 | let root = document.getElementById('root');
8 | if (!root) {
9 | root = document.createElement('div');
10 | root.id = 'root';
11 | document.body.appendChild(root);
12 | }
13 | t.context.root = root;
14 | });
15 |
16 | test('change state on click', t => {
17 | const root = t.context.root;
18 | class FooComponent extends Component {
19 | constructor(props) {
20 | super(props);
21 | this.state = {
22 | count: 0
23 | };
24 | }
25 |
26 | handleClick() {
27 | this.setState({
28 | count: this.state.count + 1
29 | });
30 | }
31 |
32 | render() {
33 | return this.handleClick()}>{this.state.count}
;
34 | }
35 | }
36 | render(, root);
37 | t.is(root.innerHTML, '0
');
38 | click(root.firstChild);
39 | t.is(root.innerHTML, '1
');
40 | });
41 |
42 | function click(dom) {
43 | var evt = document.createEvent('MouseEvent');
44 | evt.initEvent("click", false, true);
45 | dom.dispatchEvent(evt);
46 | }
47 |
--------------------------------------------------------------------------------
/codes/vue/src/core/util/error.js:
--------------------------------------------------------------------------------
1 | import config from '../config'
2 | import {
3 | warn
4 | } from './debug'
5 | import {
6 | inBrowser,
7 | inWeex
8 | } from './env'
9 |
10 | export function handleError(err, vm, info) {
11 | if (vm) {
12 | let cur = vm
13 | while ((cur = cur.$parent)) {
14 | const hooks = cur.$options.errorCaptured
15 | if (hooks) {
16 | for (let i = 0; i < hooks.length; i++) {
17 | try {
18 | const capture = hooks[i].call(cur, err, vm, info) === false
19 | if (capture) return
20 | } catch (e) {
21 | globalHandleError(e, cur, 'errorCaptured hook')
22 | }
23 | }
24 | }
25 | }
26 | }
27 | globalHandleError(err, vm, info)
28 | }
29 |
30 | function globalHandleError(err, vm, info) {
31 | if (config.errorHandler) {
32 | try {
33 | return config.errorHandler.call(null, err, vm, info)
34 | } catch (e) {
35 | logError(e, null, 'config.errorHandler')
36 | }
37 | }
38 | logError(err, vm, info)
39 | }
40 |
41 | function logError(err, vm, info) {
42 | if (process.env.NODE_ENV !== 'production') {
43 | warn(`Error in ${info}: "${err.toString()}"`, vm)
44 | }
45 | /* istanbul ignore else */
46 | if ((inBrowser || inWeex) && typeof console !== 'undefined') {
47 | console.error(err)
48 | } else {
49 | throw err
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/codes/vue/src/core/observer/dep.js:
--------------------------------------------------------------------------------
1 | import {
2 | remove
3 | } from '../util/index'
4 |
5 | let uid = 0
6 |
7 | /**
8 | * A dep is an observable that can have multiple
9 | * directives subscribing to it.
10 | * 一个 dep 就是一个 observable ,可以有多个指令订阅它。
11 | */
12 | export default class Dep {
13 | constructor() {
14 | this.id = uid++
15 | this.subs = []
16 | }
17 |
18 | // 添加订阅者
19 | addSub(sub) {
20 | this.subs.push(sub)
21 | }
22 |
23 | // 删除订阅者
24 | removeSub(sub) {
25 | remove(this.subs, sub)
26 | }
27 |
28 | // 收集依赖(把自身添加到taget的依赖列表里)
29 | depend() {
30 | if (Dep.target) {
31 | Dep.target.addDep(this)
32 | }
33 | }
34 |
35 | // 通知订阅者
36 | notify() {
37 | // stabilize the subscriber list first
38 | // 复制当前订阅者列表(slice浅拷贝),防止当前的订阅者列表受
39 | // add/remove 影响。
40 | const subs = this.subs.slice()
41 | // 逐一通知订阅者更新
42 | for (let i = 0, l = subs.length; i < l; i++) {
43 | subs[i].update()
44 | }
45 | }
46 | }
47 |
48 | // the current target watcher being evaluated.
49 | // this is globally unique because there could be only one
50 | // watcher being evaluated at any time.
51 | Dep.target = null
52 | const targetStack = []
53 |
54 | export function pushTarget(_target) {
55 | if (Dep.target) targetStack.push(Dep.target)
56 | Dep.target = _target
57 | }
58 |
59 | export function popTarget() {
60 | Dep.target = targetStack.pop()
61 | }
62 |
--------------------------------------------------------------------------------
/codes/didact/test/01.render-jsx-dom-elements.test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import './_browser-mock';
3 | /** @jsx createElement */
4 | import { render, createElement } from '../lib';
5 |
6 | test.beforeEach(t => {
7 | let root = document.getElementById('root');
8 | if (!root) {
9 | root = document.createElement('div');
10 | root.id = 'root';
11 | document.body.appendChild(root);
12 | }
13 | t.context.root = root;
14 | });
15 |
16 | test('render jsx div', t => {
17 | const root = t.context.root;
18 | const element = ;
19 | render(element, root);
20 | t.is(root.innerHTML, '');
21 | });
22 |
23 | test('render div with children', t => {
24 | const root = t.context.root;
25 | const element = (
26 |
30 | );
31 | render(element, root);
32 | t.is(root.innerHTML, '');
33 | });
34 |
35 | test('render div with props', t => {
36 | const root = t.context.root;
37 | const element = ;
38 | render(element, root);
39 | t.is(root.innerHTML, '');
40 | });
41 |
42 | test('render span with text child', t => {
43 | const root = t.context.root;
44 | const element = Foo;
45 | render(element, root);
46 | t.is(root.innerHTML, 'Foo');
47 | });
48 |
--------------------------------------------------------------------------------
/codes/didact/test/04.components.test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import './_browser-mock';
3 | /** @jsx createElement */
4 | import { render, createElement, Component } from '../lib';
5 |
6 | test.beforeEach(t => {
7 | let root = document.getElementById('root');
8 | if (!root) {
9 | root = document.createElement('div');
10 | root.id = 'root';
11 | document.body.appendChild(root);
12 | }
13 | t.context.root = root;
14 | });
15 |
16 | test('render component', t => {
17 | const root = t.context.root;
18 | class FooComponent extends Component {
19 | render() {
20 | return (
21 |
25 | );
26 | }
27 | }
28 | render(, root);
29 | t.is(root.innerHTML, '');
30 | });
31 |
32 | test('render component with props', t => {
33 | const root = t.context.root;
34 | class FooComponent extends Component {
35 | render() {
36 | return (
37 |
38 |
{this.props.name}
39 |
40 |
41 | );
42 | }
43 | }
44 | render(, root);
45 | t.is(root.innerHTML, '');
46 | });
47 |
--------------------------------------------------------------------------------
/codes/snabbdom/demo/app.js:
--------------------------------------------------------------------------------
1 | const patch = snabbdom.patch
2 | const h = snabbdom.h
3 |
4 | const container = document.getElementById('container')
5 |
6 | const vnode = h('div', {id: 'container'}, [
7 | h('span', {style: {fontWeight: 'bold'}}, 'This is bold'),
8 | ' and this is just normal text',
9 | h('a', {
10 | props: {href: '/foo'}
11 | }, 'I\'ll take you places!')
12 | ])
13 |
14 | console.log(vnode)
15 | patch(container, vnode)
16 |
17 | const newVnode = h('div', {id: 'container'}, [
18 | h('span', {style: {fontWeight: 'normal', fontStyle: 'italic'}}, 'This is now italic type'),
19 | ' and this is still just normal text',
20 | h('a', {
21 | props: {href: '/bar'}
22 | }, 'I\'ll take you places!')
23 | ])
24 | setTimeout(() => {
25 | patch(vnode, newVnode)
26 | }, 7000)
27 |
28 |
29 | const footer = document.getElementById('footer')
30 |
31 | function getList(count) {
32 | return h(
33 | 'ul',
34 | {
35 | id: 'footer',
36 | className: 'hello hi',
37 | style: {
38 | color: '#666'
39 | }
40 | },
41 | Array.apply(null, {length: count})
42 | .map((v, i) => i + 1)
43 | .map(n => h(
44 | 'li',
45 | {
46 | className: 'item',
47 | key: n
48 | },
49 | `number is ${n}`
50 | ))
51 | )
52 | }
53 | const sVnode = getList(3)
54 | patch(footer, sVnode)
55 |
56 | setTimeout(() => {
57 | patch(sVnode, getList(10))
58 | }, 4000)
59 |
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/render-helpers/render-slot.js:
--------------------------------------------------------------------------------
1 | /* */
2 |
3 | import { extend, warn, isObject } from 'core/util/index'
4 |
5 | /**
6 | * Runtime helper for rendering
7 | */
8 | export function renderSlot (
9 | name ,
10 | fallback ,
11 | props ,
12 | bindObject
13 | ) {
14 | const scopedSlotFn = this.$scopedSlots[name]
15 | let nodes
16 | if (scopedSlotFn) { // scoped slot
17 | props = props || {}
18 | if (bindObject) {
19 | if (process.env.NODE_ENV !== 'production' && !isObject(bindObject)) {
20 | warn(
21 | 'slot v-bind without argument expects an Object',
22 | this
23 | )
24 | }
25 | props = extend(extend({}, bindObject), props)
26 | }
27 | nodes = scopedSlotFn(props) || fallback
28 | } else {
29 | const slotNodes = this.$slots[name]
30 | // warn duplicate slot usage
31 | if (slotNodes) {
32 | if (process.env.NODE_ENV !== 'production' && slotNodes._rendered) {
33 | warn(
34 | `Duplicate presence of slot "${name}" found in the same render tree ` +
35 | `- this will likely cause render errors.`,
36 | this
37 | )
38 | }
39 | slotNodes._rendered = true
40 | }
41 | nodes = slotNodes || fallback
42 | }
43 |
44 | const target = props && props.slot
45 | if (target) {
46 | return this.$createElement('template', { slot: target }, nodes)
47 | } else {
48 | return nodes
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/examples/webpack-demo/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "ignorePatterns": ["dist", "node_modules"],
3 | "rules": {
4 | "max-len": [
5 | "error",
6 | {
7 | "ignoreUrls": true,
8 | "code": 80
9 | }
10 | ],
11 | "prefer-template": "off",
12 | "indent": ["error", 2],
13 | "object-curly-spacing": ["error", "always"],
14 | "no-multiple-empty-lines": [
15 | "error",
16 | {
17 | "max": 1,
18 | "maxEOF": 1
19 | }
20 | ],
21 | "no-var": "error",
22 | "one-var": ["error", "never"],
23 | "camelcase": "error",
24 | "no-multi-assign": "error",
25 | "quotes": ["error", "single"],
26 | "no-array-constructor": "error",
27 | "no-new-object": "error",
28 | "no-new-wrappers": "error",
29 | "no-nested-ternary": "error",
30 | "no-console": [
31 | "error",
32 | {
33 | "allow": ["error"]
34 | }
35 | ],
36 | "no-template-curly-in-string": "error",
37 | "no-self-compare": "error",
38 | "func-names": ["error", "as-needed"],
39 | "semi": [2, "never"],
40 | "import/no-extraneous-dependencies": ["off", { "devDependencies": false }]
41 | },
42 | "env": {
43 | "browser": true,
44 | "es6": true
45 | },
46 | "extends": ["eslint:recommended", "airbnb-base", "prettier"],
47 | "globals": {
48 | "Atomics": "readonly",
49 | "SharedArrayBuffer": "readonly"
50 | },
51 | "parserOptions": {
52 | "ecmaVersion": 11,
53 | "sourceType": "module"
54 | },
55 | "plugins": ["prettier"],
56 | "settings": { "import/resolver": "webpack" }
57 | }
58 |
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/render-helpers/bind-object-props.js:
--------------------------------------------------------------------------------
1 | /* */
2 |
3 | import config from 'core/config'
4 |
5 | import {
6 | warn,
7 | isObject,
8 | toObject,
9 | isReservedAttribute
10 | } from 'core/util/index'
11 |
12 | /**
13 | * Runtime helper for merging v-bind="object" into a VNode's data.
14 | */
15 | export function bindObjectProps (
16 | data ,
17 | tag ,
18 | value ,
19 | asProp ,
20 | isSync
21 | ) {
22 | if (value) {
23 | if (!isObject(value)) {
24 | process.env.NODE_ENV !== 'production' && warn(
25 | 'v-bind without argument expects an Object or Array value',
26 | this
27 | )
28 | } else {
29 | if (Array.isArray(value)) {
30 | value = toObject(value)
31 | }
32 | let hash
33 | for (const key in value) {
34 | if (
35 | key === 'class' ||
36 | key === 'style' ||
37 | isReservedAttribute(key)
38 | ) {
39 | hash = data
40 | } else {
41 | const type = data.attrs && data.attrs.type
42 | hash = asProp || config.mustUseProp(tag, type, key)
43 | ? data.domProps || (data.domProps = {})
44 | : data.attrs || (data.attrs = {})
45 | }
46 | if (!(key in hash)) {
47 | hash[key] = value[key]
48 |
49 | if (isSync) {
50 | const on = data.on || (data.on = {})
51 | on[`update:${key}`] = function ($event) {
52 | value[key] = $event
53 | }
54 | }
55 | }
56 | }
57 | }
58 | }
59 | return data
60 | }
61 |
--------------------------------------------------------------------------------
/examples/webpack-demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack-boilerplate",
3 | "version": "2.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "start": "cross-env NODE_ENV=development webpack serve --config config/webpack.dev.js",
8 | "build": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js",
9 | "lint": "eslint . src config || true"
10 | },
11 | "keywords": [
12 | "webpack",
13 | "webpack 5",
14 | "webpack boilerplate"
15 | ],
16 | "devDependencies": {
17 | "@babel/core": "^7.12.1",
18 | "@babel/plugin-proposal-class-properties": "^7.12.1",
19 | "@babel/preset-env": "^7.12.1",
20 | "babel-loader": "^8.1.0",
21 | "clean-webpack-plugin": "^3.0.0",
22 | "copy-webpack-plugin": "^6.2.1",
23 | "cross-env": "^7.0.2",
24 | "css-loader": "^5.0.0",
25 | "css-minimizer-webpack-plugin": "^1.1.5",
26 | "eslint": "^7.12.1",
27 | "eslint-config-airbnb-base": "^14.2.0",
28 | "eslint-config-prettier": "^6.15.0",
29 | "eslint-import-resolver-webpack": "^0.13.0",
30 | "eslint-plugin-import": "^2.22.1",
31 | "eslint-plugin-prettier": "^3.1.4",
32 | "eslint-webpack-plugin": "^2.1.0",
33 | "html-webpack-plugin": "^5.0.0-alpha.7",
34 | "mini-css-extract-plugin": "^1.0.0",
35 | "node-sass": "^4.14.1",
36 | "postcss-loader": "^4.0.4",
37 | "postcss-preset-env": "^6.7.0",
38 | "prettier": "^2.1.2",
39 | "prettier-webpack-plugin": "^1.2.0",
40 | "sass-loader": "^10.0.3",
41 | "style-loader": "^2.0.0",
42 | "webpack": "^5.1.3",
43 | "webpack-cli": "^4.0.0",
44 | "webpack-dev-server": "^3.11.0",
45 | "webpack-merge": "^5.2.0"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/render-helpers/render-static.js:
--------------------------------------------------------------------------------
1 | /* */
2 |
3 | /**
4 | * Runtime helper for rendering static trees.
5 | */
6 | export function renderStatic (
7 | index ,
8 | isInFor
9 | ) {
10 | const cached = this._staticTrees || (this._staticTrees = [])
11 | let tree = cached[index]
12 | // if has already-rendered static tree and not inside v-for,
13 | // we can reuse the same tree.
14 | if (tree && !isInFor) {
15 | return tree
16 | }
17 | // otherwise, render a fresh tree.
18 | tree = cached[index] = this.$options.staticRenderFns[index].call(
19 | this._renderProxy,
20 | null,
21 | this // for render fns generated for functional component templates
22 | )
23 | markStatic(tree, `__static__${index}`, false)
24 | return tree
25 | }
26 |
27 | /**
28 | * Runtime helper for v-once.
29 | * Effectively it means marking the node as static with a unique key.
30 | */
31 | export function markOnce (
32 | tree ,
33 | index ,
34 | key
35 | ) {
36 | markStatic(tree, `__once__${index}${key ? `_${key}` : ``}`, true)
37 | return tree
38 | }
39 |
40 | function markStatic (
41 | tree ,
42 | key ,
43 | isOnce
44 | ) {
45 | if (Array.isArray(tree)) {
46 | for (let i = 0; i < tree.length; i++) {
47 | if (tree[i] && typeof tree[i] !== 'string') {
48 | markStaticNode(tree[i], `${key}_${i}`, isOnce)
49 | }
50 | }
51 | } else {
52 | markStaticNode(tree, key, isOnce)
53 | }
54 | }
55 |
56 | function markStaticNode (node, key, isOnce) {
57 | node.isStatic = true
58 | node.key = key
59 | node.isOnce = isOnce
60 | }
61 |
--------------------------------------------------------------------------------
/codes/snabbdom/src/domApi.js:
--------------------------------------------------------------------------------
1 | /**
2 | * DOM 操作 API
3 | * 包括元素 创建/删除/插入/... 等等
4 | */
5 |
6 | function createElement(tagName) {
7 | return document.createElement(tagName)
8 | }
9 |
10 | function createElementNS(namespaceURI, qualifiedName) {
11 | return document.createElementNS(namespaceURI, qualifiedName)
12 | }
13 |
14 | function createTextNode(text) {
15 | return document.createTextNode(text)
16 | }
17 |
18 | function createComment(text) {
19 | return document.createComment(text)
20 | }
21 |
22 | function insertBefore(parentNode, newNode, referenceNode) {
23 | parentNode.insertBefore(newNode, referenceNode)
24 | }
25 |
26 | function removeChild(node, child) {
27 | node.removeChild(child)
28 | }
29 |
30 | function appendChild(node, child) {
31 | node.appendChild(child)
32 | }
33 |
34 | function parentNode(node) {
35 | return node.parentNode
36 | }
37 |
38 | function nextSibling(node) {
39 | return node.nextSibling
40 | }
41 |
42 | function tagName(elm) {
43 | return elm.tagName
44 | }
45 |
46 | function setTextContent(node, text) {
47 | node.textContent = text
48 | }
49 |
50 | function getTextContent(node) {
51 | return node.textContent
52 | }
53 |
54 | function isElement(node) {
55 | return node.nodeType === 1
56 | }
57 |
58 | function isText(node) {
59 | return node.nodeType === 3
60 | }
61 |
62 | function isComment(node) {
63 | return node.nodeType === 8
64 | }
65 |
66 | export const htmlDomApi = {
67 | createElement,
68 | createElementNS,
69 | createTextNode,
70 | createComment,
71 | insertBefore,
72 | removeChild,
73 | appendChild,
74 | parentNode,
75 | nextSibling,
76 | tagName,
77 | setTextContent,
78 | getTextContent,
79 | isElement,
80 | isText,
81 | isComment,
82 | }
83 |
84 | export default htmlDomApi
85 |
--------------------------------------------------------------------------------
/codes/snabbdom/src/modules/style.js:
--------------------------------------------------------------------------------
1 | /**
2 | * style 模块:支持 vnode 使用 style 来操作内连样式。
3 | */
4 |
5 | const raf = (typeof window !== 'undefined' && window.requestAnimationFrame) || setTimeout
6 |
7 | const nextFrame = function(fn) {
8 | raf(function() { raf(fn) })
9 | }
10 |
11 | function setNextFrame(obj, prop, val) {
12 | nextFrame(function() {
13 | obj[prop] = val
14 | })
15 | }
16 |
17 | function updateStyle(oldVnode, vnode) {
18 | let oldStyle = oldVnode.data.style
19 | let style = vnode.data.style
20 | const elm = vnode.elm
21 | let name, cur
22 |
23 | if (!oldStyle && !style) return
24 | if (oldStyle === style) return
25 | oldStyle = oldStyle || {}
26 | style = style || {}
27 |
28 | let oldHasDel = 'delayed' in oldStyle
29 |
30 | // 删除 style 中没有而 oldStyle 中有的属性
31 | for (name in oldStyle) {
32 | if (!style[name]) {
33 | if (name[0] === '-' && name[1] === '-') {
34 | elm.style.removeProperty(name)
35 | } else {
36 | elm.style[name] = ''
37 | }
38 | }
39 | }
40 | // 更新 style
41 | for (name in style) {
42 | cur = style[name]
43 | // delayed
44 | if (name === 'delayed') {
45 | for (name in style.delayed) {
46 | cur = style.delayed[name]
47 | if (!oldHasDel || cur !== oldStyle.delayed[name]) {
48 | setNextFrame(elm.style, name, cur)
49 | }
50 | }
51 | }
52 | // 普通
53 | else if (name !== 'remove' && cur !== oldStyle[name]) {
54 | if (name[0] === '-' && name[1] === '-') {
55 | elm.style.setProperty(name, cur)
56 | } else {
57 | elm.style[name] = cur
58 | }
59 | }
60 | }
61 | }
62 |
63 | export const styleModule = {
64 | create: updateStyle,
65 | update: updateStyle
66 | }
67 | export default styleModule
68 |
--------------------------------------------------------------------------------
/codes/vue/src/core/global-api/index.js:
--------------------------------------------------------------------------------
1 | import config from '../config'
2 | import {
3 | initUse
4 | } from './use'
5 | import {
6 | initMixin
7 | } from './mixin'
8 | import {
9 | initExtend
10 | } from './extend'
11 | import {
12 | initAssetRegisters
13 | } from './assets'
14 | import {
15 | set,
16 | del
17 | } from '../observer/index'
18 | import {
19 | ASSET_TYPES
20 | } from '../../shared/constants'
21 | import builtInComponents from '../components/index'
22 |
23 | import {
24 | warn,
25 | extend,
26 | nextTick,
27 | mergeOptions,
28 | defineReactive
29 | } from '../util/index'
30 |
31 | export function initGlobalAPI(Vue) {
32 | // config
33 | const configDef = {}
34 | configDef.get = () => config
35 | if (process.env.NODE_ENV !== 'production') {
36 | configDef.set = () => {
37 | warn(
38 | 'Do not replace the Vue.config object, set individual fields instead.'
39 | )
40 | }
41 | }
42 | Object.defineProperty(Vue, 'config', configDef)
43 |
44 | // exposed util methods.
45 | // NOTE: these are not considered part of the public API - avoid relying on
46 | // them unless you are aware of the risk.
47 | Vue.util = {
48 | warn,
49 | extend,
50 | mergeOptions,
51 | defineReactive
52 | }
53 |
54 | Vue.set = set
55 | Vue.delete = del
56 | Vue.nextTick = nextTick
57 |
58 | Vue.options = Object.create(null)
59 | ASSET_TYPES.forEach(type => {
60 | Vue.options[type + 's'] = Object.create(null)
61 | })
62 |
63 | // this is used to identify the "base" constructor to extend all plain-object
64 | // components with in Weex's multi-instance scenarios.
65 | Vue.options._base = Vue
66 |
67 | extend(Vue.options.components, builtInComponents)
68 |
69 | initUse(Vue)
70 | initMixin(Vue)
71 | initExtend(Vue)
72 | initAssetRegisters(Vue)
73 | }
74 |
--------------------------------------------------------------------------------
/examples/webpack-demo/config/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const paths = require('./paths')
2 | const { merge } = require('webpack-merge')
3 | const common = require('./webpack.common.js')
4 |
5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
6 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
7 |
8 | module.exports = merge(common, {
9 | mode: 'production',
10 | devtool: false,
11 | output: {
12 | path: paths.build,
13 | publicPath: '/',
14 | filename: 'js/[name].[contenthash].bundle.js',
15 | chunkLoadingGlobal: 'webpackChunkwebpack',
16 | },
17 | module: {
18 | rules: [
19 | {
20 | test: /\.(scss|css)$/,
21 | use: [
22 | MiniCssExtractPlugin.loader,
23 | {
24 | loader: 'css-loader',
25 | options: {
26 | importLoaders: 2,
27 | sourceMap: false,
28 | modules: true,
29 | },
30 | },
31 | 'postcss-loader',
32 | 'sass-loader',
33 | ],
34 | },
35 | ],
36 | },
37 | plugins: [
38 | // Extracts CSS into separate files
39 | // Note: style-loader is for development, MiniCssExtractPlugin is for production
40 | new MiniCssExtractPlugin({
41 | filename: 'styles/[name].[contenthash].css',
42 | chunkFilename: '[id].css',
43 | }),
44 | ],
45 | optimization: {
46 | minimize: false,
47 | minimizer: [new CssMinimizerPlugin(), "..."],
48 | // Once your build outputs multiple chunks, this option will ensure they share the webpack runtime
49 | // instead of having their own. This also helps with long-term caching, since the chunks will only
50 | // change when actual code changes, not the webpack runtime.
51 | runtimeChunk: {
52 | name: 'runtime',
53 | },
54 | chunkIds: 'named',
55 | },
56 | performance: {
57 | hints: false,
58 | maxEntrypointSize: 512000,
59 | maxAssetSize: 512000,
60 | },
61 | })
62 |
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/render-helpers/resolve-slots.js:
--------------------------------------------------------------------------------
1 | /* */
2 |
3 |
4 |
5 | /**
6 | * Runtime helper for resolving raw children VNodes into a slot object.
7 | */
8 | export function resolveSlots (
9 | children ,
10 | context
11 | ) {
12 | const slots = {}
13 | if (!children) {
14 | return slots
15 | }
16 | for (let i = 0, l = children.length; i < l; i++) {
17 | const child = children[i]
18 | const data = child.data
19 | // remove slot attribute if the node is resolved as a Vue slot node
20 | if (data && data.attrs && data.attrs.slot) {
21 | delete data.attrs.slot
22 | }
23 | // named slots should only be respected if the vnode was rendered in the
24 | // same context.
25 | if ((child.context === context || child.fnContext === context) &&
26 | data && data.slot != null
27 | ) {
28 | const name = data.slot
29 | const slot = (slots[name] || (slots[name] = []))
30 | if (child.tag === 'template') {
31 | slot.push.apply(slot, child.children || [])
32 | } else {
33 | slot.push(child)
34 | }
35 | } else {
36 | (slots.default || (slots.default = [])).push(child)
37 | }
38 | }
39 | // ignore slots that contains only whitespace
40 | for (const name in slots) {
41 | if (slots[name].every(isWhitespace)) {
42 | delete slots[name]
43 | }
44 | }
45 | return slots
46 | }
47 |
48 | function isWhitespace (node ) {
49 | return (node.isComment && !node.asyncFactory) || node.text === ' '
50 | }
51 |
52 | export function resolveScopedSlots (
53 | fns , // see flow/vnode
54 | res
55 | ) {
56 | res = res || {}
57 | for (let i = 0; i < fns.length; i++) {
58 | if (Array.isArray(fns[i])) {
59 | resolveScopedSlots(fns[i], res)
60 | } else {
61 | res[fns[i].key] = fns[i].fn
62 | }
63 | }
64 | return res
65 | }
66 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/helpers/extract-props.js:
--------------------------------------------------------------------------------
1 | import {
2 | tip,
3 | hasOwn,
4 | isDef,
5 | isUndef,
6 | hyphenate,
7 | formatComponentName
8 | } from '../../util/index'
9 |
10 | export function extractPropsFromVNodeData(
11 | data,
12 | Ctor,
13 | tag
14 | ) {
15 | // we are only extracting raw values here.
16 | // validation and default values are handled in the child
17 | // component itself.
18 | const propOptions = Ctor.options.props
19 | if (isUndef(propOptions)) {
20 | return
21 | }
22 | const res = {}
23 | const {
24 | attrs,
25 | props
26 | } = data
27 | if (isDef(attrs) || isDef(props)) {
28 | for (const key in propOptions) {
29 | const altKey = hyphenate(key)
30 | if (process.env.NODE_ENV !== 'production') {
31 | const keyInLowerCase = key.toLowerCase()
32 | if (
33 | key !== keyInLowerCase &&
34 | attrs && hasOwn(attrs, keyInLowerCase)
35 | ) {
36 | tip(
37 | `Prop "${keyInLowerCase}" is passed to component ` +
38 | `${formatComponentName(tag || Ctor)}, but the declared prop name is` +
39 | ` "${key}". ` +
40 | `Note that HTML attributes are case-insensitive and camelCased ` +
41 | `props need to use their kebab-case equivalents when using in-DOM ` +
42 | `templates. You should probably use "${altKey}" instead of "${key}".`
43 | )
44 | }
45 | }
46 | checkProp(res, props, key, altKey, true) ||
47 | checkProp(res, attrs, key, altKey, false)
48 | }
49 | }
50 | return res
51 | }
52 |
53 | function checkProp(
54 | res,
55 | hash,
56 | key,
57 | altKey,
58 | preserve
59 | ) {
60 | if (isDef(hash)) {
61 | if (hasOwn(hash, key)) {
62 | res[key] = hash[key]
63 | if (!preserve) {
64 | delete hash[key]
65 | }
66 | return true
67 | } else if (hasOwn(hash, altKey)) {
68 | res[key] = hash[altKey]
69 | if (!preserve) {
70 | delete hash[altKey]
71 | }
72 | return true
73 | }
74 | }
75 | return false
76 | }
77 |
--------------------------------------------------------------------------------
/codes/didact/src/interface.ts:
--------------------------------------------------------------------------------
1 | import { Component } from './component';
2 |
3 | // Fiber 标签
4 | export enum ITag {
5 | HOST_COMPONENT = 'host',
6 | CLASS_COMPONENT = 'class',
7 | HOST_ROOT = 'root',
8 | }
9 |
10 | // Effect 标签
11 | export enum Effect {
12 | PLACEMENT = 1,
13 | DELETION = 2,
14 | UPDATE = 3,
15 | }
16 |
17 | export interface IdleDeadline {
18 | didTimeout: boolean;
19 | timeRemaining(): number;
20 | }
21 |
22 | export type IdleRequestCallback = (deadline: IdleDeadline) => any;
23 |
24 | export type ComponentType = string | (() => object);
25 |
26 | export interface IState {
27 | [key: string]: any;
28 | }
29 |
30 | export interface IVNode {
31 | type: ComponentType;
32 | props: {
33 | children?: IVNode[],
34 | [key: string]: any,
35 | };
36 | }
37 |
38 | export interface IProps {
39 | children?: IVNode[];
40 | style?: object;
41 | [key: string]: any;
42 | }
43 |
44 | export interface IFiber {
45 | tag: ITag;
46 | type?: ComponentType;
47 |
48 | // parent/child/sibling 用于构建 fiber tree,对应相应的组件树。
49 | parent?: IFiber | null;
50 | child?: IFiber | null;
51 | sibling?: IFiber | null;
52 |
53 | // 大多数时候,我们有2棵fiber树:
54 | // 1. 一棵对应已经渲染到DOM的,我们称之为 current tree / old tree;
55 | // 2. 一棵是我们正在创建的,对应新的更新(setState() 或者 React.render()),叫 work-in-progress tree。
56 | // ⚠️ work-in-progress tree 不和 old tree 共享任何 fiber;一旦 work-in-progress tree 创建
57 | // 完成并完成需要的 DOM 更新,work-in-progress tree 即变成 old tree 。
58 | // alternate 用于 work-in-progress fiber 链接/指向(link)到它们对应的 old tree 上的 fiber。
59 | // fiber 和它的 alternate 共享 tag, type 和 stateNode。
60 | alternate?: IFiber | null;
61 |
62 | // 指向组件实例的引用,可以是 DOM element 或者 Class Component 的实例
63 | stateNode?: Element | Component;
64 |
65 | props: IProps;
66 | partialState?: IState | null;
67 | effectTag?: Effect;
68 | effects?: IFiber[];
69 | }
70 |
71 | export interface IUpdate {
72 | from: ITag;
73 | dom?: HTMLElement;
74 | instance?: Component;
75 | newProps?: IProps;
76 | partialState?: IState | null;
77 | }
78 |
--------------------------------------------------------------------------------
/codes/vue/src/core/config.js:
--------------------------------------------------------------------------------
1 | import {
2 | no,
3 | noop,
4 | identity
5 | } from '../shared/util'
6 |
7 | import {
8 | LIFECYCLE_HOOKS
9 | } from '../shared/constants'
10 |
11 | export default ({
12 | /**
13 | * Option merge strategies (used in core/util/options)
14 | */
15 | // $flow-disable-line
16 | optionMergeStrategies: Object.create(null),
17 |
18 | /**
19 | * Whether to suppress warnings.
20 | */
21 | silent: false,
22 |
23 | /**
24 | * Show production mode tip message on boot?
25 | */
26 | productionTip: process.env.NODE_ENV !== 'production',
27 |
28 | /**
29 | * Whether to enable devtools
30 | */
31 | devtools: process.env.NODE_ENV !== 'production',
32 |
33 | /**
34 | * Whether to record perf
35 | */
36 | performance: false,
37 |
38 | /**
39 | * Error handler for watcher errors
40 | */
41 | errorHandler: null,
42 |
43 | /**
44 | * Warn handler for watcher warns
45 | */
46 | warnHandler: null,
47 |
48 | /**
49 | * Ignore certain custom elements
50 | */
51 | ignoredElements: [],
52 |
53 | /**
54 | * Custom user key aliases for v-on
55 | */
56 | // $flow-disable-line
57 | keyCodes: Object.create(null),
58 |
59 | /**
60 | * Check if a tag is reserved so that it cannot be registered as a
61 | * component. This is platform-dependent and may be overwritten.
62 | */
63 | isReservedTag: no,
64 |
65 | /**
66 | * Check if an attribute is reserved so that it cannot be used as a component
67 | * prop. This is platform-dependent and may be overwritten.
68 | */
69 | isReservedAttr: no,
70 |
71 | /**
72 | * Check if a tag is an unknown element.
73 | * Platform-dependent.
74 | */
75 | isUnknownElement: no,
76 |
77 | /**
78 | * Get the namespace of an element
79 | */
80 | getTagNamespace: noop,
81 |
82 | /**
83 | * Parse the real tag name for the specific platform.
84 | */
85 | parsePlatformTagName: identity,
86 |
87 | /**
88 | * Check if an attribute must be bound using property, e.g. value
89 | * Platform-dependent.
90 | */
91 | mustUseProp: no,
92 |
93 | /**
94 | * Exposed for legacy reasons
95 | */
96 | _lifecycleHooks: LIFECYCLE_HOOKS
97 | })
98 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/helpers/update-listeners.js:
--------------------------------------------------------------------------------
1 | import {
2 | warn
3 | } from '../../util/index'
4 | import {
5 | cached,
6 | isUndef,
7 | isPlainObject
8 | } from '../../../shared/util'
9 |
10 | const normalizeEvent = cached((name) => {
11 | const passive = name.charAt(0) === '&'
12 | name = passive ? name.slice(1) : name
13 | const once = name.charAt(0) === '~' // Prefixed last, checked first
14 | name = once ? name.slice(1) : name
15 | const capture = name.charAt(0) === '!'
16 | name = capture ? name.slice(1) : name
17 | return {
18 | name,
19 | once,
20 | capture,
21 | passive
22 | }
23 | })
24 |
25 | export function createFnInvoker(fns) {
26 | function invoker() {
27 | const fns = invoker.fns
28 | if (Array.isArray(fns)) {
29 | const cloned = fns.slice()
30 | for (let i = 0; i < cloned.length; i++) {
31 | cloned[i].apply(null, arguments)
32 | }
33 | } else {
34 | // return handler return value for single handlers
35 | return fns.apply(null, arguments)
36 | }
37 | }
38 | invoker.fns = fns
39 | return invoker
40 | }
41 |
42 | export function updateListeners(
43 | on,
44 | oldOn,
45 | add,
46 | remove,
47 | vm
48 | ) {
49 | let name, def, cur, old, event
50 | for (name in on) {
51 | def = cur = on[name]
52 | old = oldOn[name]
53 | event = normalizeEvent(name)
54 | /* istanbul ignore if */
55 | if (__WEEX__ && isPlainObject(def)) {
56 | cur = def.handler
57 | event.params = def.params
58 | }
59 | if (isUndef(cur)) {
60 | process.env.NODE_ENV !== 'production' && warn(
61 | `Invalid handler for event "${event.name}": got ` + String(cur),
62 | vm
63 | )
64 | } else if (isUndef(old)) {
65 | if (isUndef(cur.fns)) {
66 | cur = on[name] = createFnInvoker(cur)
67 | }
68 | add(event.name, cur, event.once, event.capture, event.passive, event.params)
69 | } else if (cur !== old) {
70 | old.fns = cur
71 | on[name] = old
72 | }
73 | }
74 | for (name in oldOn) {
75 | if (isUndef(on[name])) {
76 | event = normalizeEvent(name)
77 | remove(event.name, oldOn[name], event.capture)
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/codes/snabbdom/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Creeper
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
24 | The MIT License (MIT)
25 |
26 | Copyright (c) 2015 Simon Friis Vindum
27 |
28 | Permission is hereby granted, free of charge, to any person obtaining a copy
29 | of this software and associated documentation files (the "Software"), to deal
30 | in the Software without restriction, including without limitation the rights
31 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
32 | copies of the Software, and to permit persons to whom the Software is
33 | furnished to do so, subject to the following conditions:
34 |
35 | The above copyright notice and this permission notice shall be included in all
36 | copies or substantial portions of the Software.
37 |
38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44 | SOFTWARE.
--------------------------------------------------------------------------------
/examples/webpack-demo/config/webpack.common.js:
--------------------------------------------------------------------------------
1 | const { CleanWebpackPlugin } = require('clean-webpack-plugin')
2 | const CopyWebpackPlugin = require('copy-webpack-plugin')
3 | const HtmlWebpackPlugin = require('html-webpack-plugin')
4 | const PrettierPlugin = require('prettier-webpack-plugin')
5 | const ESLintPlugin = require('eslint-webpack-plugin')
6 | const paths = require('./paths')
7 |
8 | module.exports = {
9 | // Where webpack looks to start building the bundle
10 | entry: [paths.src + '/index.js'],
11 |
12 | // Where webpack outputs the assets and bundles
13 | output: {
14 | path: paths.build,
15 | filename: '[name].bundle.js',
16 | publicPath: '/',
17 | },
18 |
19 | // Customize the webpack build process
20 | plugins: [
21 | // Removes/cleans build folders and unused assets when rebuilding
22 | new CleanWebpackPlugin(),
23 |
24 | // Copies files from target to destination folder
25 | new CopyWebpackPlugin({
26 | patterns: [
27 | {
28 | from: paths.public,
29 | to: 'assets',
30 | globOptions: {
31 | ignore: ['*.DS_Store'],
32 | },
33 | noErrorOnMissing: true,
34 | },
35 | ],
36 | }),
37 |
38 | // Generates an HTML file from a template
39 | // Generates deprecation warning: https://github.com/jantimon/html-webpack-plugin/issues/1501
40 | new HtmlWebpackPlugin({
41 | title: 'webpack-demo',
42 | favicon: paths.src + '/images/favicon.png',
43 | template: paths.src + '/template.html', // template file
44 | filename: 'index.html', // output file
45 | }),
46 |
47 | // ESLint configuration
48 | new ESLintPlugin({
49 | files: ['.', 'src', 'config'],
50 | formatter: 'table',
51 | }),
52 |
53 | // Prettier configuration
54 | new PrettierPlugin(),
55 | ],
56 |
57 | // Determine how modules within the project are treated
58 | module: {
59 | rules: [
60 | // JavaScript: Use Babel to transpile JavaScript files
61 | { test: /\.js$/, use: ['babel-loader'] },
62 |
63 | // Images: Copy image files to build folder
64 | { test: /\.(?:ico|gif|png|jpg|jpeg)$/i, type: 'asset/resource' },
65 |
66 | // Fonts and SVGs: Inline files
67 | { test: /\.(woff(2)?|eot|ttf|otf|svg|)$/, type: 'asset/inline' },
68 | ],
69 | },
70 | }
71 |
--------------------------------------------------------------------------------
/codes/didact/test/00.render-dom-elements.test.js:
--------------------------------------------------------------------------------
1 | import test from 'ava';
2 | import './_browser-mock';
3 | import { render } from '../lib';
4 |
5 | test.beforeEach(t => {
6 | let root = document.getElementById('root');
7 | if (!root) {
8 | root = document.createElement('div');
9 | root.id = 'root';
10 | document.body.appendChild(root);
11 | }
12 | t.context.root = root;
13 | });
14 |
15 | test('render div', t => {
16 | const root = t.context.root;
17 | const element = {
18 | type: 'div',
19 | props: {}
20 | };
21 | render(element, root);
22 | t.is(root.innerHTML, '');
23 | });
24 |
25 | test('render div with children', t => {
26 | const root = t.context.root;
27 | const element = {
28 | type: 'div',
29 | props: {
30 | children: [
31 | { type: 'b', props: {} },
32 | { type: 'a', props: { href: 'foo' } }
33 | ]
34 | }
35 | };
36 | render(element, root);
37 | t.is(root.innerHTML, '');
38 | });
39 |
40 | test('render div with props', t => {
41 | const root = t.context.root;
42 | const element = {
43 | type: 'div',
44 | props: { id: 'foo' }
45 | };
46 | render(element, root);
47 | t.is(root.innerHTML, '');
48 | });
49 |
50 | test('render span with text child', t => {
51 | const root = t.context.root;
52 | const element = {
53 | type: 'span',
54 | props: {
55 | children: [
56 | {
57 | type: 'TEXT ELEMENT',
58 | props: { nodeValue: 'Foo' }
59 | }
60 | ]
61 | }
62 | };
63 | render(element, root);
64 | t.is(root.innerHTML, 'Foo');
65 | });
66 |
67 | test('render supports element array', t => {
68 | const root = t.context.root;
69 | const elements = [{
70 | type: 'span',
71 | props: {
72 | children: [
73 | {
74 | type: 'TEXT ELEMENT',
75 | props: { nodeValue: 'Foo' }
76 | }
77 | ]
78 | }
79 | }, {
80 | type: 'div',
81 | props: { id: 'foo' }
82 | }];
83 | render(elements, root);
84 | t.is(root.innerHTML, 'Foo');
85 | });
86 |
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/inject.js:
--------------------------------------------------------------------------------
1 | import {
2 | hasOwn
3 | } from 'shared/util'
4 | import {
5 | warn,
6 | hasSymbol
7 | } from '../util/index'
8 | import {
9 | defineReactive,
10 | toggleObserving
11 | } from '../observer/index'
12 |
13 | export function initProvide(vm) {
14 | const provide = vm.$options.provide
15 | if (provide) {
16 | vm._provided = typeof provide === 'function' ?
17 | provide.call(vm) :
18 | provide
19 | }
20 | }
21 |
22 | export function initInjections(vm) {
23 | const result = resolveInject(vm.$options.inject, vm)
24 | if (result) {
25 | toggleObserving(false)
26 | Object.keys(result).forEach(key => {
27 | /* istanbul ignore else */
28 | if (process.env.NODE_ENV !== 'production') {
29 | defineReactive(vm, key, result[key], () => {
30 | warn(
31 | `Avoid mutating an injected value directly since the changes will be ` +
32 | `overwritten whenever the provided component re-renders. ` +
33 | `injection being mutated: "${key}"`,
34 | vm
35 | )
36 | })
37 | } else {
38 | defineReactive(vm, key, result[key])
39 | }
40 | })
41 | toggleObserving(true)
42 | }
43 | }
44 |
45 | export function resolveInject(inject, vm) {
46 | if (inject) {
47 | // inject is :any because flow is not smart enough to figure out cached
48 | const result = Object.create(null)
49 | const keys = hasSymbol ?
50 | Reflect.ownKeys(inject).filter(key => {
51 | /* istanbul ignore next */
52 | return Object.getOwnPropertyDescriptor(inject, key).enumerable
53 | }) :
54 | Object.keys(inject)
55 |
56 | for (let i = 0; i < keys.length; i++) {
57 | const key = keys[i]
58 | const provideKey = inject[key].from
59 | let source = vm
60 | while (source) {
61 | if (source._provided && hasOwn(source._provided, provideKey)) {
62 | result[key] = source._provided[provideKey]
63 | break
64 | }
65 | source = source.$parent
66 | }
67 | if (!source) {
68 | if ('default' in inject[key]) {
69 | const provideDefault = inject[key].default
70 | result[key] = typeof provideDefault === 'function' ?
71 | provideDefault.call(vm) :
72 | provideDefault
73 | } else if (process.env.NODE_ENV !== 'production') {
74 | warn(`Injection "${key}" not found`, vm)
75 | }
76 | }
77 | }
78 | return result
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/proxy.js:
--------------------------------------------------------------------------------
1 | /* not type checking this file because flow doesn't play well with Proxy */
2 |
3 | import config from 'core/config'
4 | import { warn, makeMap, isNative } from '../util/index'
5 |
6 | let initProxy
7 |
8 | if (process.env.NODE_ENV !== 'production') {
9 | const allowedGlobals = makeMap(
10 | 'Infinity,undefined,NaN,isFinite,isNaN,' +
11 | 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
12 | 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
13 | 'require' // for Webpack/Browserify
14 | )
15 |
16 | const warnNonPresent = (target, key) => {
17 | warn(
18 | `Property or method "${key}" is not defined on the instance but ` +
19 | 'referenced during render. Make sure that this property is reactive, ' +
20 | 'either in the data option, or for class-based components, by ' +
21 | 'initializing the property. ' +
22 | 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',
23 | target
24 | )
25 | }
26 |
27 | const hasProxy =
28 | typeof Proxy !== 'undefined' && isNative(Proxy)
29 |
30 | if (hasProxy) {
31 | const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact')
32 | config.keyCodes = new Proxy(config.keyCodes, {
33 | set (target, key, value) {
34 | if (isBuiltInModifier(key)) {
35 | warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)
36 | return false
37 | } else {
38 | target[key] = value
39 | return true
40 | }
41 | }
42 | })
43 | }
44 |
45 | const hasHandler = {
46 | has (target, key) {
47 | const has = key in target
48 | const isAllowed = allowedGlobals(key) || (typeof key === 'string' && key.charAt(0) === '_')
49 | if (!has && !isAllowed) {
50 | warnNonPresent(target, key)
51 | }
52 | return has || !isAllowed
53 | }
54 | }
55 |
56 | const getHandler = {
57 | get (target, key) {
58 | if (typeof key === 'string' && !(key in target)) {
59 | warnNonPresent(target, key)
60 | }
61 | return target[key]
62 | }
63 | }
64 |
65 | initProxy = function initProxy (vm) {
66 | if (hasProxy) {
67 | // determine which proxy handler to use
68 | const options = vm.$options
69 | const handlers = options.render && options.render._withStripped
70 | ? getHandler
71 | : hasHandler
72 | vm._renderProxy = new Proxy(vm, handlers)
73 | } else {
74 | vm._renderProxy = vm
75 | }
76 | }
77 | }
78 |
79 | export { initProxy }
80 |
--------------------------------------------------------------------------------
/codes/didact/src/dom-utils.ts:
--------------------------------------------------------------------------------
1 | import { TEXT_ELEMENT } from './element';
2 | import { IFiber, IProps, IState } from './interface';
3 |
4 | const isEvent = (name: string) => name.startsWith('on');
5 | const isAttribute = (name: string) =>
6 | !isEvent(name) && name !== 'children' && name !== 'style';
7 | const isNew = (prev: IState, next: IState) => (key: string) => prev[key] !== next[key];
8 | const isGone = (next: IState) => (key: string) => !(key in next);
9 |
10 | export function updateDomProperties(dom: HTMLElement, prevProps: IProps, nextProps: IProps) {
11 | // 解绑之前注册的事件
12 | Object.keys(prevProps)
13 | .filter(isEvent)
14 | .filter(
15 | (key) => !(key in nextProps) || isNew(prevProps, nextProps)(key),
16 | )
17 | .forEach((name) => {
18 | const eventType = name.toLowerCase().substring(2);
19 | dom.removeEventListener(eventType, prevProps[name]);
20 | });
21 |
22 | // 删除已去除的 attributes
23 | Object.keys(prevProps)
24 | .filter(isAttribute)
25 | .filter(isGone(nextProps))
26 | .forEach((name) => {
27 | (dom as IState)[name] = null;
28 | });
29 |
30 | // 设置添加或更新的 attributes
31 | Object.keys(nextProps)
32 | .filter(isAttribute)
33 | .filter(isNew(prevProps, nextProps))
34 | .forEach((name) => {
35 | (dom as IState)[name] = nextProps[name];
36 | });
37 |
38 | // 重新绑定事件
39 | Object.keys(nextProps)
40 | .filter(isEvent)
41 | .filter(isNew(prevProps, nextProps))
42 | .forEach((name) => {
43 | const eventType = name.toLowerCase().substring(2);
44 | dom.addEventListener(eventType, nextProps[name]);
45 | });
46 |
47 | prevProps.style = prevProps.style || {};
48 | nextProps.style = nextProps.style || {};
49 | // 更新 style
50 | Object.keys(nextProps.style)
51 | .filter(isNew(prevProps.style, nextProps.style))
52 | .forEach((key) => {
53 | dom.style[key as any] = (nextProps as any).style[key];
54 | });
55 | // 删除已去除的 style
56 | Object.keys(prevProps.style)
57 | .filter(isGone(nextProps.style))
58 | .forEach((key) => {
59 | dom.style[key as any] = '';
60 | });
61 | }
62 |
63 | /**
64 | * 创建对应的 DOM 元素,并根据 props 设置相应属性
65 | * @param fiber 目标 fiber
66 | */
67 | export function createDomElement(fiber: IFiber) {
68 | const isTextElement = fiber.type === TEXT_ELEMENT;
69 | const dom = isTextElement
70 | ? document.createTextNode('')
71 | : document.createElement(fiber.type as string);
72 | updateDomProperties(dom as HTMLElement, [], fiber.props);
73 | return dom;
74 | }
75 |
--------------------------------------------------------------------------------
/codes/minipack/src/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const parser = require('@babel/parser');
4 | const traverse = require('@babel/traverse').default;
5 | const babel = require('@babel/core');
6 |
7 | let ID = 0;
8 |
9 | function createAsset(filename) {
10 | if (path.extname(filename) === '') {
11 | filename += '.js';
12 | }
13 | const content = fs.readFileSync(filename, { encoding: 'utf8' });
14 | const deps = [];
15 | const id = ID++;
16 | const ast = parser.parse(content, {
17 | sourceType: 'module'
18 | });
19 |
20 | traverse(ast, {
21 | enter(path) {
22 | if (path.node.type === 'ImportDeclaration') {
23 | deps.push(path.node.source.value);
24 | }
25 | }
26 | });
27 |
28 | const code = babel.transformFromAstSync(ast, content, {
29 | presets: [['@babel/preset-env']]
30 | }).code;
31 |
32 | return {
33 | id,
34 | filename,
35 | ast,
36 | code,
37 | deps
38 | }
39 | }
40 |
41 | function createGraph(entry) {
42 | const mainAsset = createAsset(
43 | path.isAbsolute(entry) ? entry : path.resolve(entry)
44 | );
45 | const queue = [mainAsset];
46 |
47 | for(const asset of queue) {
48 | const dirname = path.dirname(asset.filename);
49 |
50 | asset.mapping = {};
51 |
52 | asset.deps.forEach(relativePath => {
53 | const absolutePath = path.join(dirname, relativePath);
54 |
55 | const child = createAsset(absolutePath);
56 |
57 | asset.mapping[relativePath] = child.id;
58 |
59 | queue.push(child);
60 | });
61 | }
62 | return queue;
63 | }
64 |
65 | function bundle(graph) {
66 | let modules = '';
67 |
68 | graph.forEach(mod => {
69 | modules += `${mod.id}: [
70 | function (require, module, exports) {
71 | ${mod.code}
72 | },
73 | ${JSON.stringify(mod.mapping)}
74 | ],`;
75 | });
76 |
77 | const result = `
78 | (function(modules) {
79 | function require(id) {
80 | const [fn, mapping] = modules[id];
81 |
82 | function localRequire(relativePath) {
83 | return require(mapping[relativePath])
84 | }
85 |
86 | const module = { exports: {} };
87 |
88 | fn(localRequire, module, module.exports);
89 |
90 | return module.exports;
91 | }
92 |
93 | require(0);
94 | })({${modules}})
95 | `;
96 |
97 | return result;
98 | }
99 |
100 | const graph = createGraph('./demo/entry.js');
101 |
102 | const result = bundle(graph);
103 |
104 | eval(result)
105 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/vnode.js:
--------------------------------------------------------------------------------
1 | export default class VNode {
2 | // rendered in this component's scope
3 |
4 |
5 | // component instance
6 | // component placeholder node
7 |
8 | // strictly internal
9 | // contains raw HTML? (server only)
10 | // hoisted static node
11 | // necessary for enter transition check
12 | // empty comment placeholder?
13 | // is a cloned node?
14 | // is a v-once node?
15 | // async component factory function
16 |
17 |
18 |
19 | // real context vm for functional nodes
20 | // for SSR caching
21 | // functional scope id support
22 |
23 | constructor(
24 | tag,
25 | data,
26 | children,
27 | text,
28 | elm,
29 | context,
30 | componentOptions,
31 | asyncFactory
32 | ) {
33 | this.tag = tag
34 | this.data = data
35 | this.children = children
36 | this.text = text
37 | this.elm = elm
38 | this.ns = undefined
39 | this.context = context
40 | this.fnContext = undefined
41 | this.fnOptions = undefined
42 | this.fnScopeId = undefined
43 | this.key = data && data.key
44 | this.componentOptions = componentOptions
45 | this.componentInstance = undefined
46 | this.parent = undefined
47 | this.raw = false
48 | this.isStatic = false
49 | this.isRootInsert = true
50 | this.isComment = false
51 | this.isCloned = false
52 | this.isOnce = false
53 | this.asyncFactory = asyncFactory
54 | this.asyncMeta = undefined
55 | this.isAsyncPlaceholder = false
56 | }
57 |
58 | // DEPRECATED: alias for componentInstance for backwards compat.
59 | /* istanbul ignore next */
60 | get child() {
61 | return this.componentInstance
62 | }
63 | }
64 |
65 | export const createEmptyVNode = (text = '') => {
66 | const node = new VNode()
67 | node.text = text
68 | node.isComment = true
69 | return node
70 | }
71 |
72 | export function createTextVNode(val) {
73 | return new VNode(undefined, undefined, undefined, String(val))
74 | }
75 |
76 | // optimized shallow clone
77 | // used for static nodes and slot nodes because they may be reused across
78 | // multiple renders, cloning them avoids errors when DOM manipulations rely
79 | // on their elm reference.
80 | export function cloneVNode(vnode) {
81 | const cloned = new VNode(
82 | vnode.tag,
83 | vnode.data,
84 | vnode.children,
85 | vnode.text,
86 | vnode.elm,
87 | vnode.context,
88 | vnode.componentOptions,
89 | vnode.asyncFactory
90 | )
91 | cloned.ns = vnode.ns
92 | cloned.isStatic = vnode.isStatic
93 | cloned.key = vnode.key
94 | cloned.isComment = vnode.isComment
95 | cloned.fnContext = vnode.fnContext
96 | cloned.fnOptions = vnode.fnOptions
97 | cloned.fnScopeId = vnode.fnScopeId
98 | cloned.asyncMeta = vnode.asyncMeta
99 | cloned.isCloned = true
100 | return cloned
101 | }
102 |
--------------------------------------------------------------------------------
/codes/vue/src/core/util/env.js:
--------------------------------------------------------------------------------
1 | // can we use __proto__?
2 | export const hasProto = '__proto__' in {}
3 |
4 | // Browser environment sniffing
5 | export const inBrowser = typeof window !== 'undefined'
6 | export const inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform
7 | export const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase()
8 | export const UA = inBrowser && window.navigator.userAgent.toLowerCase()
9 | export const isIE = UA && /msie|trident/.test(UA)
10 | export const isIE9 = UA && UA.indexOf('msie 9.0') > 0
11 | export const isEdge = UA && UA.indexOf('edge/') > 0
12 | export const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android')
13 | export const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios')
14 | export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge
15 |
16 | // Firefox has a "watch" function on Object.prototype...
17 | export const nativeWatch = ({}).watch
18 |
19 | export let supportsPassive = false
20 | if (inBrowser) {
21 | try {
22 | const opts = {}
23 | Object.defineProperty(opts, 'passive', ({
24 | get() {
25 | /* istanbul ignore next */
26 | supportsPassive = true
27 | }
28 | })) // https://github.com/facebook/flow/issues/285
29 | window.addEventListener('test-passive', null, opts)
30 | } catch (e) {}
31 | }
32 |
33 | // this needs to be lazy-evaled because vue may be required before
34 | // vue-server-renderer can set VUE_ENV
35 | let _isServer
36 | export const isServerRendering = () => {
37 | if (_isServer === undefined) {
38 | /* istanbul ignore if */
39 | if (!inBrowser && !inWeex && typeof global !== 'undefined') {
40 | // detect presence of vue-server-renderer and avoid
41 | // Webpack shimming the process
42 | _isServer = global['process'].env.VUE_ENV === 'server'
43 | } else {
44 | _isServer = false
45 | }
46 | }
47 | return _isServer
48 | }
49 |
50 | // detect devtools
51 | export const devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__
52 |
53 | /* istanbul ignore next */
54 | export function isNative(Ctor) {
55 | return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
56 | }
57 |
58 | export const hasSymbol =
59 | typeof Symbol !== 'undefined' && isNative(Symbol) &&
60 | typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys)
61 |
62 | let _Set
63 | /* istanbul ignore if */ // $flow-disable-line
64 | if (typeof Set !== 'undefined' && isNative(Set)) {
65 | // use native Set when available.
66 | _Set = Set
67 | } else {
68 | // a non-standard Set polyfill that only works with primitive keys.
69 | _Set = class Set {
70 |
71 | constructor() {
72 | this.set = Object.create(null)
73 | }
74 | has(key) {
75 | return this.set[key] === true
76 | }
77 | add(key) {
78 | this.set[key] = true
79 | }
80 | clear() {
81 | this.set = Object.create(null)
82 | }
83 | }
84 | }
85 |
86 | export {
87 | _Set
88 | }
89 |
--------------------------------------------------------------------------------
/codes/vue/src/core/global-api/extend.js:
--------------------------------------------------------------------------------
1 | import {
2 | ASSET_TYPES
3 | } from '../../shared/constants'
4 | import {
5 | defineComputed,
6 | proxy
7 | } from '../instance/state'
8 | import {
9 | extend,
10 | mergeOptions,
11 | validateComponentName
12 | } from '../util/index'
13 |
14 | export function initExtend(Vue) {
15 | /**
16 | * Each instance constructor, including Vue, has a unique
17 | * cid. This enables us to create wrapped "child
18 | * constructors" for prototypal inheritance and cache them.
19 | */
20 | Vue.cid = 0
21 | let cid = 1
22 |
23 | /**
24 | * Class inheritance
25 | */
26 | Vue.extend = function (extendOptions) {
27 | extendOptions = extendOptions || {}
28 | const Super = this
29 | const SuperId = Super.cid
30 | const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
31 | if (cachedCtors[SuperId]) {
32 | return cachedCtors[SuperId]
33 | }
34 |
35 | const name = extendOptions.name || Super.options.name
36 | if (process.env.NODE_ENV !== 'production' && name) {
37 | validateComponentName(name)
38 | }
39 |
40 | const Sub = function VueComponent(options) {
41 | this._init(options)
42 | }
43 | Sub.prototype = Object.create(Super.prototype)
44 | Sub.prototype.constructor = Sub
45 | Sub.cid = cid++
46 | Sub.options = mergeOptions(
47 | Super.options,
48 | extendOptions
49 | )
50 | Sub['super'] = Super
51 |
52 | // For props and computed properties, we define the proxy getters on
53 | // the Vue instances at extension time, on the extended prototype. This
54 | // avoids Object.defineProperty calls for each instance created.
55 | if (Sub.options.props) {
56 | initProps(Sub)
57 | }
58 | if (Sub.options.computed) {
59 | initComputed(Sub)
60 | }
61 |
62 | // allow further extension/mixin/plugin usage
63 | Sub.extend = Super.extend
64 | Sub.mixin = Super.mixin
65 | Sub.use = Super.use
66 |
67 | // create asset registers, so extended classes
68 | // can have their private assets too.
69 | ASSET_TYPES.forEach(function (type) {
70 | Sub[type] = Super[type]
71 | })
72 | // enable recursive self-lookup
73 | if (name) {
74 | Sub.options.components[name] = Sub
75 | }
76 |
77 | // keep a reference to the super options at extension time.
78 | // later at instantiation we can check if Super's options have
79 | // been updated.
80 | Sub.superOptions = Super.options
81 | Sub.extendOptions = extendOptions
82 | Sub.sealedOptions = extend({}, Sub.options)
83 |
84 | // cache constructor
85 | cachedCtors[SuperId] = Sub
86 | return Sub
87 | }
88 | }
89 |
90 | function initProps(Comp) {
91 | const props = Comp.options.props
92 | for (const key in props) {
93 | proxy(Comp.prototype, `_props`, key)
94 | }
95 | }
96 |
97 | function initComputed(Comp) {
98 | const computed = Comp.options.computed
99 | for (const key in computed) {
100 | defineComputed(Comp.prototype, key, computed[key])
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/codes/vue/src/core/util/debug.js:
--------------------------------------------------------------------------------
1 | import config from '../config'
2 | import {
3 | noop
4 | } from '../../shared/util'
5 |
6 | export let warn = noop
7 | export let tip = noop
8 | export let generateComponentTrace = (noop) // work around flow check
9 | export let formatComponentName = (noop)
10 |
11 | if (process.env.NODE_ENV !== 'production') {
12 | const hasConsole = typeof console !== 'undefined'
13 | const classifyRE = /(?:^|[-_])(\w)/g
14 | const classify = str => str
15 | .replace(classifyRE, c => c.toUpperCase())
16 | .replace(/[-_]/g, '')
17 |
18 | warn = (msg, vm) => {
19 | const trace = vm ? generateComponentTrace(vm) : ''
20 |
21 | if (config.warnHandler) {
22 | config.warnHandler.call(null, msg, vm, trace)
23 | } else if (hasConsole && (!config.silent)) {
24 | console.error(`[Vue warn]: ${msg}${trace}`)
25 | }
26 | }
27 |
28 | tip = (msg, vm) => {
29 | if (hasConsole && (!config.silent)) {
30 | console.warn(`[Vue tip]: ${msg}` + (
31 | vm ? generateComponentTrace(vm) : ''
32 | ))
33 | }
34 | }
35 |
36 | formatComponentName = (vm, includeFile) => {
37 | if (vm.$root === vm) {
38 | return ''
39 | }
40 | const options = typeof vm === 'function' && vm.cid != null ?
41 | vm.options :
42 | vm._isVue ?
43 | vm.$options || vm.constructor.options :
44 | vm || {}
45 | let name = options.name || options._componentTag
46 | const file = options.__file
47 | if (!name && file) {
48 | const match = file.match(/([^/\\]+)\.vue$/)
49 | name = match && match[1]
50 | }
51 |
52 | return (
53 | (name ? `<${classify(name)}>` : ``) +
54 | (file && includeFile !== false ? ` at ${file}` : '')
55 | )
56 | }
57 |
58 | const repeat = (str, n) => {
59 | let res = ''
60 | while (n) {
61 | if (n % 2 === 1) res += str
62 | if (n > 1) str += str
63 | n >>= 1
64 | }
65 | return res
66 | }
67 |
68 | generateComponentTrace = vm => {
69 | if (vm._isVue && vm.$parent) {
70 | const tree = []
71 | let currentRecursiveSequence = 0
72 | while (vm) {
73 | if (tree.length > 0) {
74 | const last = tree[tree.length - 1]
75 | if (last.constructor === vm.constructor) {
76 | currentRecursiveSequence++
77 | vm = vm.$parent
78 | continue
79 | } else if (currentRecursiveSequence > 0) {
80 | tree[tree.length - 1] = [last, currentRecursiveSequence]
81 | currentRecursiveSequence = 0
82 | }
83 | }
84 | tree.push(vm)
85 | vm = vm.$parent
86 | }
87 | return '\n\nfound in\n\n' + tree
88 | .map((vm, i) => `${
89 | i === 0 ? '---> ' : repeat(' ', 5 + i * 2)
90 | }${
91 | Array.isArray(vm)
92 | ? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)`
93 | : formatComponentName(vm)
94 | }`)
95 | .join('\n')
96 | } else {
97 | return `\n\n(found in ${formatComponentName(vm)})`
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/helpers/normalize-children.js:
--------------------------------------------------------------------------------
1 | import VNode, { createTextVNode } from '../../vdom/vnode'
2 | import {
3 | isFalse,
4 | isTrue,
5 | isDef,
6 | isUndef,
7 | isPrimitive
8 | } from '../../../shared/util'
9 |
10 | // The template compiler attempts to minimize the need for normalization by
11 | // statically analyzing the template at compile time.
12 | //
13 | // For plain HTML markup, normalization can be completely skipped because the
14 | // generated render function is guaranteed to return Array. There are
15 | // two cases where extra normalization is needed:
16 |
17 | // 1. When the children contains components - because a functional component
18 | // may return an Array instead of a single root. In this case, just a simple
19 | // normalization is needed - if any child is an Array, we flatten the whole
20 | // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep
21 | // because functional components already normalize their own children.
22 | export function simpleNormalizeChildren (children ) {
23 | for (let i = 0; i < children.length; i++) {
24 | if (Array.isArray(children[i])) {
25 | return Array.prototype.concat.apply([], children)
26 | }
27 | }
28 | return children
29 | }
30 |
31 | // 2. When the children contains constructs that always generated nested Arrays,
32 | // e.g. , , v-for, or when the children is provided by user
33 | // with hand-written render functions / JSX. In such cases a full normalization
34 | // is needed to cater to all possible types of children values.
35 | export function normalizeChildren (children ) {
36 | return isPrimitive(children)
37 | ? [createTextVNode(children)]
38 | : Array.isArray(children)
39 | ? normalizeArrayChildren(children)
40 | : undefined
41 | }
42 |
43 | function isTextNode (node) {
44 | return isDef(node) && isDef(node.text) && isFalse(node.isComment)
45 | }
46 |
47 | function normalizeArrayChildren (children , nestedIndex ) {
48 | const res = []
49 | let i, c, lastIndex, last
50 | for (i = 0; i < children.length; i++) {
51 | c = children[i]
52 | if (isUndef(c) || typeof c === 'boolean') continue
53 | lastIndex = res.length - 1
54 | last = res[lastIndex]
55 | // nested
56 | if (Array.isArray(c)) {
57 | if (c.length > 0) {
58 | c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)
59 | // merge adjacent text nodes
60 | if (isTextNode(c[0]) && isTextNode(last)) {
61 | res[lastIndex] = createTextVNode(last.text + (c[0] ).text)
62 | c.shift()
63 | }
64 | res.push.apply(res, c)
65 | }
66 | } else if (isPrimitive(c)) {
67 | if (isTextNode(last)) {
68 | // merge adjacent text nodes
69 | // this is necessary for SSR hydration because text nodes are
70 | // essentially merged when rendered to HTML strings
71 | res[lastIndex] = createTextVNode(last.text + c)
72 | } else if (c !== '') {
73 | // convert primitive to vnode
74 | res.push(createTextVNode(c))
75 | }
76 | } else {
77 | if (isTextNode(c) && isTextNode(last)) {
78 | // merge adjacent text nodes
79 | res[lastIndex] = createTextVNode(last.text + c.text)
80 | } else {
81 | // default key for nested array children (likely generated by v-for)
82 | if (isTrue(children._isVList) &&
83 | isDef(c.tag) &&
84 | isUndef(c.key) &&
85 | isDef(nestedIndex)) {
86 | c.key = `__vlist${nestedIndex}_${i}__`
87 | }
88 | res.push(c)
89 | }
90 | }
91 | }
92 | return res
93 | }
94 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/modules/directives.js:
--------------------------------------------------------------------------------
1 | /* */
2 |
3 | import { emptyNode } from 'core/vdom/patch'
4 | import { resolveAsset, handleError } from 'core/util/index'
5 | import { mergeVNodeHook } from 'core/vdom/helpers/index'
6 |
7 | export default {
8 | create: updateDirectives,
9 | update: updateDirectives,
10 | destroy: function unbindDirectives (vnode ) {
11 | updateDirectives(vnode, emptyNode)
12 | }
13 | }
14 |
15 | function updateDirectives (oldVnode , vnode ) {
16 | if (oldVnode.data.directives || vnode.data.directives) {
17 | _update(oldVnode, vnode)
18 | }
19 | }
20 |
21 | function _update (oldVnode, vnode) {
22 | const isCreate = oldVnode === emptyNode
23 | const isDestroy = vnode === emptyNode
24 | const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context)
25 | const newDirs = normalizeDirectives(vnode.data.directives, vnode.context)
26 |
27 | const dirsWithInsert = []
28 | const dirsWithPostpatch = []
29 |
30 | let key, oldDir, dir
31 | for (key in newDirs) {
32 | oldDir = oldDirs[key]
33 | dir = newDirs[key]
34 | if (!oldDir) {
35 | // new directive, bind
36 | callHook(dir, 'bind', vnode, oldVnode)
37 | if (dir.def && dir.def.inserted) {
38 | dirsWithInsert.push(dir)
39 | }
40 | } else {
41 | // existing directive, update
42 | dir.oldValue = oldDir.value
43 | callHook(dir, 'update', vnode, oldVnode)
44 | if (dir.def && dir.def.componentUpdated) {
45 | dirsWithPostpatch.push(dir)
46 | }
47 | }
48 | }
49 |
50 | if (dirsWithInsert.length) {
51 | const callInsert = () => {
52 | for (let i = 0; i < dirsWithInsert.length; i++) {
53 | callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode)
54 | }
55 | }
56 | if (isCreate) {
57 | mergeVNodeHook(vnode, 'insert', callInsert)
58 | } else {
59 | callInsert()
60 | }
61 | }
62 |
63 | if (dirsWithPostpatch.length) {
64 | mergeVNodeHook(vnode, 'postpatch', () => {
65 | for (let i = 0; i < dirsWithPostpatch.length; i++) {
66 | callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode)
67 | }
68 | })
69 | }
70 |
71 | if (!isCreate) {
72 | for (key in oldDirs) {
73 | if (!newDirs[key]) {
74 | // no longer present, unbind
75 | callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy)
76 | }
77 | }
78 | }
79 | }
80 |
81 | const emptyModifiers = Object.create(null)
82 |
83 | function normalizeDirectives (
84 | dirs ,
85 | vm
86 | ) {
87 | const res = Object.create(null)
88 | if (!dirs) {
89 | // $flow-disable-line
90 | return res
91 | }
92 | let i, dir
93 | for (i = 0; i < dirs.length; i++) {
94 | dir = dirs[i]
95 | if (!dir.modifiers) {
96 | // $flow-disable-line
97 | dir.modifiers = emptyModifiers
98 | }
99 | res[getRawDirName(dir)] = dir
100 | dir.def = resolveAsset(vm.$options, 'directives', dir.name, true)
101 | }
102 | // $flow-disable-line
103 | return res
104 | }
105 |
106 | function getRawDirName (dir ) {
107 | return dir.rawName || `${dir.name}.${Object.keys(dir.modifiers || {}).join('.')}`
108 | }
109 |
110 | function callHook (dir, hook, vnode, oldVnode, isDestroy) {
111 | const fn = dir.def && dir.def[hook]
112 | if (fn) {
113 | try {
114 | fn(vnode.elm, dir, vnode, oldVnode, isDestroy)
115 | } catch (e) {
116 | handleError(e, vnode.context, `directive ${dir.name} ${hook} hook`)
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/codes/vue/src/core/components/keep-alive.js:
--------------------------------------------------------------------------------
1 | import {
2 | isRegExp,
3 | remove
4 | } from '../../shared/util'
5 | import {
6 | getFirstComponentChild
7 | } from 'core/vdom/helpers/index'
8 |
9 | function getComponentName(opts) {
10 | return opts && (opts.Ctor.options.name || opts.tag)
11 | }
12 |
13 | function matches(pattern, name) {
14 | if (Array.isArray(pattern)) {
15 | return pattern.indexOf(name) > -1
16 | } else if (typeof pattern === 'string') {
17 | return pattern.split(',').indexOf(name) > -1
18 | } else if (isRegExp(pattern)) {
19 | return pattern.test(name)
20 | }
21 | /* istanbul ignore next */
22 | return false
23 | }
24 |
25 | function pruneCache(keepAliveInstance, filter) {
26 | const {
27 | cache,
28 | keys,
29 | _vnode
30 | } = keepAliveInstance
31 | for (const key in cache) {
32 | const cachedNode = cache[key]
33 | if (cachedNode) {
34 | const name = getComponentName(cachedNode.componentOptions)
35 | if (name && !filter(name)) {
36 | pruneCacheEntry(cache, key, keys, _vnode)
37 | }
38 | }
39 | }
40 | }
41 |
42 | function pruneCacheEntry(
43 | cache,
44 | key,
45 | keys,
46 | current
47 | ) {
48 | const cached = cache[key]
49 | if (cached && (!current || cached.tag !== current.tag)) {
50 | cached.componentInstance.$destroy()
51 | }
52 | cache[key] = null
53 | remove(keys, key)
54 | }
55 |
56 | const patternTypes = [String, RegExp, Array]
57 |
58 | export default {
59 | name: 'keep-alive',
60 | abstract: true,
61 |
62 | props: {
63 | include: patternTypes,
64 | exclude: patternTypes,
65 | max: [String, Number]
66 | },
67 |
68 | created() {
69 | this.cache = Object.create(null)
70 | this.keys = []
71 | },
72 |
73 | destroyed() {
74 | for (const key in this.cache) {
75 | pruneCacheEntry(this.cache, key, this.keys)
76 | }
77 | },
78 |
79 | mounted() {
80 | this.$watch('include', val => {
81 | pruneCache(this, name => matches(val, name))
82 | })
83 | this.$watch('exclude', val => {
84 | pruneCache(this, name => !matches(val, name))
85 | })
86 | },
87 |
88 | render() {
89 | const slot = this.$slots.default
90 | const vnode = getFirstComponentChild(slot)
91 | const componentOptions = vnode && vnode.componentOptions
92 | if (componentOptions) {
93 | // check pattern
94 | const name = getComponentName(componentOptions)
95 | const {
96 | include,
97 | exclude
98 | } = this
99 | if (
100 | // not included
101 | (include && (!name || !matches(include, name))) ||
102 | // excluded
103 | (exclude && name && matches(exclude, name))
104 | ) {
105 | return vnode
106 | }
107 |
108 | const {
109 | cache,
110 | keys
111 | } = this
112 | const key = vnode.key == null
113 | // same constructor may get registered as different local components
114 | // so cid alone is not enough (#3269)
115 | ?
116 | componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') :
117 | vnode.key
118 | if (cache[key]) {
119 | vnode.componentInstance = cache[key].componentInstance
120 | // make current key freshest
121 | remove(keys, key)
122 | keys.push(key)
123 | } else {
124 | cache[key] = vnode
125 | keys.push(key)
126 | // prune oldest entry
127 | if (this.max && keys.length > parseInt(this.max)) {
128 | pruneCacheEntry(cache, keys[0], keys, this._vnode)
129 | }
130 | }
131 |
132 | vnode.data.keepAlive = true
133 | }
134 | return vnode || (slot && slot[0])
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/events.js:
--------------------------------------------------------------------------------
1 | import {
2 | tip,
3 | toArray,
4 | hyphenate,
5 | handleError,
6 | formatComponentName
7 | } from '../util/index'
8 | import {
9 | updateListeners
10 | } from '../vdom/helpers/index'
11 |
12 | export function initEvents(vm) {
13 | vm._events = Object.create(null)
14 | vm._hasHookEvent = false
15 | // init parent attached events
16 | const listeners = vm.$options._parentListeners
17 | if (listeners) {
18 | updateComponentListeners(vm, listeners)
19 | }
20 | }
21 |
22 | let target
23 |
24 | function add(event, fn, once) {
25 | if (once) {
26 | target.$once(event, fn)
27 | } else {
28 | target.$on(event, fn)
29 | }
30 | }
31 |
32 | function remove(event, fn) {
33 | target.$off(event, fn)
34 | }
35 |
36 | export function updateComponentListeners(
37 | vm,
38 | listeners,
39 | oldListeners
40 | ) {
41 | target = vm
42 | updateListeners(listeners, oldListeners || {}, add, remove, vm)
43 | target = undefined
44 | }
45 |
46 | export function eventsMixin(Vue) {
47 | const hookRE = /^hook:/
48 | Vue.prototype.$on = function (event, fn) {
49 | const vm = this
50 | if (Array.isArray(event)) {
51 | for (let i = 0, l = event.length; i < l; i++) {
52 | this.$on(event[i], fn)
53 | }
54 | } else {
55 | (vm._events[event] || (vm._events[event] = [])).push(fn)
56 | // optimize hook:event cost by using a boolean flag marked at registration
57 | // instead of a hash lookup
58 | if (hookRE.test(event)) {
59 | vm._hasHookEvent = true
60 | }
61 | }
62 | return vm
63 | }
64 |
65 | Vue.prototype.$once = function (event, fn) {
66 | const vm = this
67 |
68 | function on() {
69 | vm.$off(event, on)
70 | fn.apply(vm, arguments)
71 | }
72 | on.fn = fn
73 | vm.$on(event, on)
74 | return vm
75 | }
76 |
77 | Vue.prototype.$off = function (event, fn) {
78 | const vm = this
79 | // all
80 | if (!arguments.length) {
81 | vm._events = Object.create(null)
82 | return vm
83 | }
84 | // array of events
85 | if (Array.isArray(event)) {
86 | for (let i = 0, l = event.length; i < l; i++) {
87 | this.$off(event[i], fn)
88 | }
89 | return vm
90 | }
91 | // specific event
92 | const cbs = vm._events[event]
93 | if (!cbs) {
94 | return vm
95 | }
96 | if (!fn) {
97 | vm._events[event] = null
98 | return vm
99 | }
100 | if (fn) {
101 | // specific handler
102 | let cb
103 | let i = cbs.length
104 | while (i--) {
105 | cb = cbs[i]
106 | if (cb === fn || cb.fn === fn) {
107 | cbs.splice(i, 1)
108 | break
109 | }
110 | }
111 | }
112 | return vm
113 | }
114 |
115 | Vue.prototype.$emit = function (event) {
116 | const vm = this
117 | if (process.env.NODE_ENV !== 'production') {
118 | const lowerCaseEvent = event.toLowerCase()
119 | if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
120 | tip(
121 | `Event "${lowerCaseEvent}" is emitted in component ` +
122 | `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
123 | `Note that HTML attributes are case-insensitive and you cannot use ` +
124 | `v-on to listen to camelCase events when using in-DOM templates. ` +
125 | `You should probably use "${hyphenate(event)}" instead of "${event}".`
126 | )
127 | }
128 | }
129 | let cbs = vm._events[event]
130 | if (cbs) {
131 | cbs = cbs.length > 1 ? toArray(cbs) : cbs
132 | const args = toArray(arguments, 1)
133 | for (let i = 0, l = cbs.length; i < l; i++) {
134 | try {
135 | cbs[i].apply(vm, args)
136 | } catch (e) {
137 | handleError(e, vm, `event handler for "${event}"`)
138 | }
139 | }
140 | }
141 | return vm
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/helpers/resolve-async-component.js:
--------------------------------------------------------------------------------
1 | import {
2 | warn,
3 | once,
4 | isDef,
5 | isUndef,
6 | isTrue,
7 | isObject,
8 | hasSymbol
9 | } from '../../util/index'
10 |
11 | import {
12 | createEmptyVNode
13 | } from '../../vdom/vnode'
14 |
15 | function ensureCtor(comp, base) {
16 | if (
17 | comp.__esModule ||
18 | (hasSymbol && comp[Symbol.toStringTag] === 'Module')
19 | ) {
20 | comp = comp.default
21 | }
22 | return isObject(comp) ?
23 | base.extend(comp) :
24 | comp
25 | }
26 |
27 | export function createAsyncPlaceholder(
28 | factory,
29 | data,
30 | context,
31 | children,
32 | tag
33 | ) {
34 | const node = createEmptyVNode()
35 | node.asyncFactory = factory
36 | node.asyncMeta = {
37 | data,
38 | context,
39 | children,
40 | tag
41 | }
42 | return node
43 | }
44 |
45 | export function resolveAsyncComponent(
46 | factory,
47 | baseCtor,
48 | context
49 | ) {
50 | if (isTrue(factory.error) && isDef(factory.errorComp)) {
51 | return factory.errorComp
52 | }
53 |
54 | if (isDef(factory.resolved)) {
55 | return factory.resolved
56 | }
57 |
58 | if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
59 | return factory.loadingComp
60 | }
61 |
62 | if (isDef(factory.contexts)) {
63 | // already pending
64 | factory.contexts.push(context)
65 | } else {
66 | const contexts = factory.contexts = [context]
67 | let sync = true
68 |
69 | const forceRender = () => {
70 | for (let i = 0, l = contexts.length; i < l; i++) {
71 | contexts[i].$forceUpdate()
72 | }
73 | }
74 |
75 | const resolve = once((res) => {
76 | // cache resolved
77 | factory.resolved = ensureCtor(res, baseCtor)
78 | // invoke callbacks only if this is not a synchronous resolve
79 | // (async resolves are shimmed as synchronous during SSR)
80 | if (!sync) {
81 | forceRender()
82 | }
83 | })
84 |
85 | const reject = once(reason => {
86 | process.env.NODE_ENV !== 'production' && warn(
87 | `Failed to resolve async component: ${String(factory)}` +
88 | (reason ? `\nReason: ${reason}` : '')
89 | )
90 | if (isDef(factory.errorComp)) {
91 | factory.error = true
92 | forceRender()
93 | }
94 | })
95 |
96 | const res = factory(resolve, reject)
97 |
98 | if (isObject(res)) {
99 | if (typeof res.then === 'function') {
100 | // () => Promise
101 | if (isUndef(factory.resolved)) {
102 | res.then(resolve, reject)
103 | }
104 | } else if (isDef(res.component) && typeof res.component.then === 'function') {
105 | res.component.then(resolve, reject)
106 |
107 | if (isDef(res.error)) {
108 | factory.errorComp = ensureCtor(res.error, baseCtor)
109 | }
110 |
111 | if (isDef(res.loading)) {
112 | factory.loadingComp = ensureCtor(res.loading, baseCtor)
113 | if (res.delay === 0) {
114 | factory.loading = true
115 | } else {
116 | setTimeout(() => {
117 | if (isUndef(factory.resolved) && isUndef(factory.error)) {
118 | factory.loading = true
119 | forceRender()
120 | }
121 | }, res.delay || 200)
122 | }
123 | }
124 |
125 | if (isDef(res.timeout)) {
126 | setTimeout(() => {
127 | if (isUndef(factory.resolved)) {
128 | reject(
129 | process.env.NODE_ENV !== 'production' ?
130 | `timeout (${res.timeout}ms)` :
131 | null
132 | )
133 | }
134 | }, res.timeout)
135 | }
136 | }
137 | }
138 |
139 | sync = false
140 | // return in case resolved synchronously
141 | return factory.loading ?
142 | factory.loadingComp :
143 | factory.resolved
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/codes/vue/src/core/util/next-tick.js:
--------------------------------------------------------------------------------
1 | /* globals MessageChannel */
2 |
3 | import {
4 | noop
5 | } from '../../shared/util'
6 | import {
7 | handleError
8 | } from './error'
9 | import {
10 | isIOS,
11 | isNative
12 | } from './env'
13 |
14 | const callbacks = []
15 | let pending = false
16 |
17 | function flushCallbacks() {
18 | pending = false
19 | const copies = callbacks.slice(0)
20 | callbacks.length = 0
21 | for (let i = 0; i < copies.length; i++) {
22 | copies[i]()
23 | }
24 | }
25 |
26 | // Here we have async deferring wrappers using both microtasks and (macro) tasks.
27 | // In < 2.4 we used microtasks everywhere, but there are some scenarios where
28 | // microtasks have too high a priority and fire in between supposedly
29 | // sequential events (e.g. #4521, #6690) or even between bubbling of the same
30 | // event (#6566). However, using (macro) tasks everywhere also has subtle problems
31 | // when state is changed right before repaint (e.g. #6813, out-in transitions).
32 | // Here we use microtask by default, but expose a way to force (macro) task when
33 | // needed (e.g. in event handlers attached by v-on).
34 | let microTimerFunc
35 | let macroTimerFunc
36 | let useMacroTask = false
37 |
38 | // Determine (macro) task defer implementation.
39 | // Technically setImmediate should be the ideal choice, but it's only available
40 | // in IE. The only polyfill that consistently queues the callback after all DOM
41 | // events triggered in the same loop is by using MessageChannel.
42 | /* istanbul ignore if */
43 | if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
44 | macroTimerFunc = () => {
45 | setImmediate(flushCallbacks)
46 | }
47 | } else if (typeof MessageChannel !== 'undefined' && (
48 | isNative(MessageChannel) ||
49 | // PhantomJS
50 | MessageChannel.toString() === '[object MessageChannelConstructor]'
51 | )) {
52 | const channel = new MessageChannel()
53 | const port = channel.port2
54 | channel.port1.onmessage = flushCallbacks
55 | macroTimerFunc = () => {
56 | port.postMessage(1)
57 | }
58 | } else {
59 | /* istanbul ignore next */
60 | macroTimerFunc = () => {
61 | setTimeout(flushCallbacks, 0)
62 | }
63 | }
64 |
65 | // Determine microtask defer implementation.
66 | /* istanbul ignore next, $flow-disable-line */
67 | if (typeof Promise !== 'undefined' && isNative(Promise)) {
68 | const p = Promise.resolve()
69 | microTimerFunc = () => {
70 | p.then(flushCallbacks)
71 | // in problematic UIWebViews, Promise.then doesn't completely break, but
72 | // it can get stuck in a weird state where callbacks are pushed into the
73 | // microtask queue but the queue isn't being flushed, until the browser
74 | // needs to do some other work, e.g. handle a timer. Therefore we can
75 | // "force" the microtask queue to be flushed by adding an empty timer.
76 | if (isIOS) setTimeout(noop)
77 | }
78 | } else {
79 | // fallback to macro
80 | microTimerFunc = macroTimerFunc
81 | }
82 |
83 | /**
84 | * Wrap a function so that if any code inside triggers state change,
85 | * the changes are queued using a (macro) task instead of a microtask.
86 | */
87 | export function withMacroTask(fn) {
88 | return fn._withTask || (fn._withTask = function () {
89 | useMacroTask = true
90 | const res = fn.apply(null, arguments)
91 | useMacroTask = false
92 | return res
93 | })
94 | }
95 |
96 | export function nextTick(cb, ctx) {
97 | let _resolve
98 | callbacks.push(() => {
99 | if (cb) {
100 | try {
101 | cb.call(ctx)
102 | } catch (e) {
103 | handleError(e, ctx, 'nextTick')
104 | }
105 | } else if (_resolve) {
106 | _resolve(ctx)
107 | }
108 | })
109 | if (!pending) {
110 | pending = true
111 | if (useMacroTask) {
112 | macroTimerFunc()
113 | } else {
114 | microTimerFunc()
115 | }
116 | }
117 | // $flow-disable-line
118 | if (!cb && typeof Promise !== 'undefined') {
119 | return new Promise(resolve => {
120 | _resolve = resolve
121 | })
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/create-functional-component.js:
--------------------------------------------------------------------------------
1 | import VNode, {
2 | cloneVNode
3 | } from './vnode'
4 | import {
5 | createElement
6 | } from './create-element'
7 | import {
8 | resolveInject
9 | } from '../instance/inject'
10 | import {
11 | normalizeChildren
12 | } from '../vdom/helpers/normalize-children'
13 | import {
14 | resolveSlots
15 | } from '../instance/render-helpers/resolve-slots'
16 | import {
17 | installRenderHelpers
18 | } from '../instance/render-helpers/index'
19 |
20 | import {
21 | isDef,
22 | isTrue,
23 | hasOwn,
24 | camelize,
25 | emptyObject,
26 | validateProp
27 | } from '../util/index'
28 |
29 | export function FunctionalRenderContext(
30 | data,
31 | props,
32 | children,
33 | parent,
34 | Ctor
35 | ) {
36 | const options = Ctor.options
37 | // ensure the createElement function in functional components
38 | // gets a unique context - this is necessary for correct named slot check
39 | let contextVm
40 | if (hasOwn(parent, '_uid')) {
41 | contextVm = Object.create(parent)
42 | // $flow-disable-line
43 | contextVm._original = parent
44 | } else {
45 | // the context vm passed in is a functional context as well.
46 | // in this case we want to make sure we are able to get a hold to the
47 | // real context instance.
48 | contextVm = parent
49 | // $flow-disable-line
50 | parent = parent._original
51 | }
52 | const isCompiled = isTrue(options._compiled)
53 | const needNormalization = !isCompiled
54 |
55 | this.data = data
56 | this.props = props
57 | this.children = children
58 | this.parent = parent
59 | this.listeners = data.on || emptyObject
60 | this.injections = resolveInject(options.inject, parent)
61 | this.slots = () => resolveSlots(children, parent)
62 |
63 | // support for compiled functional template
64 | if (isCompiled) {
65 | // exposing $options for renderStatic()
66 | this.$options = options
67 | // pre-resolve slots for renderSlot()
68 | this.$slots = this.slots()
69 | this.$scopedSlots = data.scopedSlots || emptyObject
70 | }
71 |
72 | if (options._scopeId) {
73 | this._c = (a, b, c, d) => {
74 | const vnode = createElement(contextVm, a, b, c, d, needNormalization)
75 | if (vnode && !Array.isArray(vnode)) {
76 | vnode.fnScopeId = options._scopeId
77 | vnode.fnContext = parent
78 | }
79 | return vnode
80 | }
81 | } else {
82 | this._c = (a, b, c, d) => createElement(contextVm, a, b, c, d, needNormalization)
83 | }
84 | }
85 |
86 | installRenderHelpers(FunctionalRenderContext.prototype)
87 |
88 | export function createFunctionalComponent(
89 | Ctor,
90 | propsData,
91 | data,
92 | contextVm,
93 | children
94 | ) {
95 | const options = Ctor.options
96 | const props = {}
97 | const propOptions = options.props
98 | if (isDef(propOptions)) {
99 | for (const key in propOptions) {
100 | props[key] = validateProp(key, propOptions, propsData || emptyObject)
101 | }
102 | } else {
103 | if (isDef(data.attrs)) mergeProps(props, data.attrs)
104 | if (isDef(data.props)) mergeProps(props, data.props)
105 | }
106 |
107 | const renderContext = new FunctionalRenderContext(
108 | data,
109 | props,
110 | children,
111 | contextVm,
112 | Ctor
113 | )
114 |
115 | const vnode = options.render.call(null, renderContext._c, renderContext)
116 |
117 | if (vnode instanceof VNode) {
118 | return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options)
119 | } else if (Array.isArray(vnode)) {
120 | const vnodes = normalizeChildren(vnode) || []
121 | const res = new Array(vnodes.length)
122 | for (let i = 0; i < vnodes.length; i++) {
123 | res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options)
124 | }
125 | return res
126 | }
127 | }
128 |
129 | function cloneAndMarkFunctionalResult(vnode, data, contextVm, options) {
130 | // #7817 clone node before setting fnContext, otherwise if the node is reused
131 | // (e.g. it was from a cached normal slot) the fnContext causes named slots
132 | // that should not be matched to match.
133 | const clone = cloneVNode(vnode)
134 | clone.fnContext = contextVm
135 | clone.fnOptions = options
136 | if (data.slot) {
137 | (clone.data || (clone.data = {})).slot = data.slot
138 | }
139 | return clone
140 | }
141 |
142 | function mergeProps(to, from) {
143 | for (const key in from) {
144 | to[camelize(key)] = from[key]
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/codes/vue/src/core/observer/scheduler.js:
--------------------------------------------------------------------------------
1 | import config from '../config'
2 | import {
3 | callHook,
4 | activateChildComponent
5 | } from '../instance/lifecycle'
6 |
7 | import {
8 | warn,
9 | nextTick,
10 | devtools
11 | } from '../util/index'
12 |
13 | export const MAX_UPDATE_COUNT = 100
14 |
15 | const queue = []
16 | const activatedChildren = []
17 | let has = {}
18 | let circular = {}
19 | let waiting = false
20 | let flushing = false
21 | let index = 0
22 |
23 | /**
24 | * Reset the scheduler's state.
25 | */
26 | function resetSchedulerState() {
27 | index = queue.length = activatedChildren.length = 0
28 | has = {}
29 | if (process.env.NODE_ENV !== 'production') {
30 | circular = {}
31 | }
32 | waiting = flushing = false
33 | }
34 |
35 | /**
36 | * Flush both queues and run the watchers.
37 | */
38 | function flushSchedulerQueue() {
39 | flushing = true
40 | let watcher, id
41 |
42 | // Sort queue before flush.
43 | // This ensures that:
44 | // 1. Components are updated from parent to child. (because parent is always
45 | // created before the child)
46 | // 2. A component's user watchers are run before its render watcher (because
47 | // user watchers are created before the render watcher)
48 | // 3. If a component is destroyed during a parent component's watcher run,
49 | // its watchers can be skipped.
50 | queue.sort((a, b) => a.id - b.id)
51 |
52 | // do not cache length because more watchers might be pushed
53 | // as we run existing watchers
54 | for (index = 0; index < queue.length; index++) {
55 | watcher = queue[index]
56 | if (watcher.before) {
57 | watcher.before()
58 | }
59 | id = watcher.id
60 | has[id] = null
61 | watcher.run()
62 | // in dev build, check and stop circular updates.
63 | if (process.env.NODE_ENV !== 'production' && has[id] != null) {
64 | circular[id] = (circular[id] || 0) + 1
65 | if (circular[id] > MAX_UPDATE_COUNT) {
66 | warn(
67 | 'You may have an infinite update loop ' + (
68 | watcher.user ?
69 | `in watcher with expression "${watcher.expression}"` :
70 | `in a component render function.`
71 | ),
72 | watcher.vm
73 | )
74 | break
75 | }
76 | }
77 | }
78 |
79 | // keep copies of post queues before resetting state
80 | const activatedQueue = activatedChildren.slice()
81 | const updatedQueue = queue.slice()
82 |
83 | resetSchedulerState()
84 |
85 | // call component updated and activated hooks
86 | callActivatedHooks(activatedQueue)
87 | callUpdatedHooks(updatedQueue)
88 |
89 | // devtool hook
90 | /* istanbul ignore if */
91 | if (devtools && config.devtools) {
92 | devtools.emit('flush')
93 | }
94 | }
95 |
96 | function callUpdatedHooks(queue) {
97 | let i = queue.length
98 | while (i--) {
99 | const watcher = queue[i]
100 | const vm = watcher.vm
101 | if (vm._watcher === watcher && vm._isMounted) {
102 | callHook(vm, 'updated')
103 | }
104 | }
105 | }
106 |
107 | /**
108 | * Queue a kept-alive component that was activated during patch.
109 | * The queue will be processed after the entire tree has been patched.
110 | */
111 | export function queueActivatedComponent(vm) {
112 | // setting _inactive to false here so that a render function can
113 | // rely on checking whether it's in an inactive tree (e.g. router-view)
114 | vm._inactive = false
115 | activatedChildren.push(vm)
116 | }
117 |
118 | function callActivatedHooks(queue) {
119 | for (let i = 0; i < queue.length; i++) {
120 | queue[i]._inactive = true
121 | activateChildComponent(queue[i], true /* true */ )
122 | }
123 | }
124 |
125 | /**
126 | * Push a watcher into the watcher queue.
127 | * Jobs with duplicate IDs will be skipped unless it's
128 | * pushed when the queue is being flushed.
129 | */
130 | export function queueWatcher(watcher) {
131 | const id = watcher.id
132 | if (has[id] == null) {
133 | has[id] = true
134 | if (!flushing) {
135 | queue.push(watcher)
136 | } else {
137 | // if already flushing, splice the watcher based on its id
138 | // if already past its id, it will be run next immediately.
139 | let i = queue.length - 1
140 | while (i > index && queue[i].id > watcher.id) {
141 | i--
142 | }
143 | queue.splice(i + 1, 0, watcher)
144 | }
145 | // queue the flush
146 | if (!waiting) {
147 | waiting = true
148 | nextTick(flushSchedulerQueue)
149 | }
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/render.js:
--------------------------------------------------------------------------------
1 | import {
2 | warn,
3 | nextTick,
4 | emptyObject,
5 | handleError,
6 | defineReactive
7 | } from '../util/index'
8 |
9 | import {
10 | createElement
11 | } from '../vdom/create-element'
12 | import {
13 | installRenderHelpers
14 | } from './render-helpers/index'
15 | import {
16 | resolveSlots
17 | } from './render-helpers/resolve-slots'
18 | import VNode, {
19 | createEmptyVNode
20 | } from '../vdom/vnode'
21 |
22 | import {
23 | isUpdatingChildComponent
24 | } from './lifecycle'
25 |
26 | export function initRender(vm) {
27 | vm._vnode = null // the root of the child tree
28 | vm._staticTrees = null // v-once cached trees
29 | const options = vm.$options
30 | const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
31 | const renderContext = parentVnode && parentVnode.context
32 | vm.$slots = resolveSlots(options._renderChildren, renderContext)
33 | vm.$scopedSlots = emptyObject
34 | // bind the createElement fn to this instance
35 | // so that we get proper render context inside it.
36 | // args order: tag, data, children, normalizationType, alwaysNormalize
37 | // internal version is used by render functions compiled from templates
38 | vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
39 | // normalization is always applied for the public version, used in
40 | // user-written render functions.
41 | vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
42 |
43 | // $attrs & $listeners are exposed for easier HOC creation.
44 | // they need to be reactive so that HOCs using them are always updated
45 | const parentData = parentVnode && parentVnode.data
46 |
47 | /* istanbul ignore else */
48 | if (process.env.NODE_ENV !== 'production') {
49 | defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
50 | !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
51 | }, true)
52 | defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
53 | !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
54 | }, true)
55 | } else {
56 | defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
57 | defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
58 | }
59 | }
60 |
61 | export function renderMixin(Vue) {
62 | // install runtime convenience helpers
63 | installRenderHelpers(Vue.prototype)
64 |
65 | Vue.prototype.$nextTick = function (fn) {
66 | return nextTick(fn, this)
67 | }
68 |
69 | Vue.prototype._render = function () {
70 | const vm = this
71 | const {
72 | render,
73 | _parentVnode
74 | } = vm.$options
75 |
76 | // reset _rendered flag on slots for duplicate slot check
77 | if (process.env.NODE_ENV !== 'production') {
78 | for (const key in vm.$slots) {
79 | // $flow-disable-line
80 | vm.$slots[key]._rendered = false
81 | }
82 | }
83 |
84 | if (_parentVnode) {
85 | vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject
86 | }
87 |
88 | // set parent vnode. this allows render functions to have access
89 | // to the data on the placeholder node.
90 | vm.$vnode = _parentVnode
91 | // render self
92 | let vnode
93 | try {
94 | vnode = render.call(vm._renderProxy, vm.$createElement)
95 | } catch (e) {
96 | handleError(e, vm, `render`)
97 | // return error render result,
98 | // or previous vnode to prevent render error causing blank component
99 | /* istanbul ignore else */
100 | if (process.env.NODE_ENV !== 'production') {
101 | if (vm.$options.renderError) {
102 | try {
103 | vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
104 | } catch (e) {
105 | handleError(e, vm, `renderError`)
106 | vnode = vm._vnode
107 | }
108 | } else {
109 | vnode = vm._vnode
110 | }
111 | } else {
112 | vnode = vm._vnode
113 | }
114 | }
115 | // return empty vnode in case the render function errored out
116 | if (!(vnode instanceof VNode)) {
117 | if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
118 | warn(
119 | 'Multiple root nodes returned from render function. Render function ' +
120 | 'should return a single root node.',
121 | vm
122 | )
123 | }
124 | vnode = createEmptyVNode()
125 | }
126 | // set parent
127 | vnode.parent = _parentVnode
128 | return vnode
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/create-element.js:
--------------------------------------------------------------------------------
1 | import config from '../config'
2 | import VNode, {
3 | createEmptyVNode
4 | } from './vnode'
5 | import {
6 | createComponent
7 | } from './create-component'
8 | import {
9 | traverse
10 | } from '../observer/traverse'
11 |
12 | import {
13 | warn,
14 | isDef,
15 | isUndef,
16 | isTrue,
17 | isObject,
18 | isPrimitive,
19 | resolveAsset
20 | } from '../util/index'
21 |
22 | import {
23 | normalizeChildren,
24 | simpleNormalizeChildren
25 | } from './helpers/index'
26 |
27 | const SIMPLE_NORMALIZE = 1
28 | const ALWAYS_NORMALIZE = 2
29 |
30 | // wrapper function for providing a more flexible interface
31 | // without getting yelled at by flow
32 | export function createElement(
33 | context,
34 | tag,
35 | data,
36 | children,
37 | normalizationType,
38 | alwaysNormalize
39 | ) {
40 | if (Array.isArray(data) || isPrimitive(data)) {
41 | normalizationType = children
42 | children = data
43 | data = undefined
44 | }
45 | if (isTrue(alwaysNormalize)) {
46 | normalizationType = ALWAYS_NORMALIZE
47 | }
48 | return _createElement(context, tag, data, children, normalizationType)
49 | }
50 |
51 | export function _createElement(
52 | context,
53 | tag,
54 | data,
55 | children,
56 | normalizationType
57 | ) {
58 | if (isDef(data) && isDef((data).__ob__)) {
59 | process.env.NODE_ENV !== 'production' && warn(
60 | `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
61 | 'Always create fresh vnode data objects in each render!',
62 | context
63 | )
64 | return createEmptyVNode()
65 | }
66 | // object syntax in v-bind
67 | if (isDef(data) && isDef(data.is)) {
68 | tag = data.is
69 | }
70 | if (!tag) {
71 | // in case of component :is set to falsy value
72 | return createEmptyVNode()
73 | }
74 | // warn against non-primitive key
75 | if (process.env.NODE_ENV !== 'production' &&
76 | isDef(data) && isDef(data.key) && !isPrimitive(data.key)
77 | ) {
78 | if (!__WEEX__ || !('@binding' in data.key)) {
79 | warn(
80 | 'Avoid using non-primitive value as key, ' +
81 | 'use string/number value instead.',
82 | context
83 | )
84 | }
85 | }
86 | // support single function children as default scoped slot
87 | if (Array.isArray(children) &&
88 | typeof children[0] === 'function'
89 | ) {
90 | data = data || {}
91 | data.scopedSlots = {
92 | default: children[0]
93 | }
94 | children.length = 0
95 | }
96 | if (normalizationType === ALWAYS_NORMALIZE) {
97 | children = normalizeChildren(children)
98 | } else if (normalizationType === SIMPLE_NORMALIZE) {
99 | children = simpleNormalizeChildren(children)
100 | }
101 | let vnode, ns
102 | if (typeof tag === 'string') {
103 | let Ctor
104 | ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
105 | if (config.isReservedTag(tag)) {
106 | // platform built-in elements
107 | vnode = new VNode(
108 | config.parsePlatformTagName(tag), data, children,
109 | undefined, undefined, context
110 | )
111 | } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
112 | // component
113 | vnode = createComponent(Ctor, data, context, children, tag)
114 | } else {
115 | // unknown or unlisted namespaced elements
116 | // check at runtime because it may get assigned a namespace when its
117 | // parent normalizes children
118 | vnode = new VNode(
119 | tag, data, children,
120 | undefined, undefined, context
121 | )
122 | }
123 | } else {
124 | // direct component options / constructor
125 | vnode = createComponent(tag, data, context, children)
126 | }
127 | if (Array.isArray(vnode)) {
128 | return vnode
129 | } else if (isDef(vnode)) {
130 | if (isDef(ns)) applyNS(vnode, ns)
131 | if (isDef(data)) registerDeepBindings(data)
132 | return vnode
133 | } else {
134 | return createEmptyVNode()
135 | }
136 | }
137 |
138 | function applyNS(vnode, ns, force) {
139 | vnode.ns = ns
140 | if (vnode.tag === 'foreignObject') {
141 | // use default namespace inside foreignObject
142 | ns = undefined
143 | force = true
144 | }
145 | if (isDef(vnode.children)) {
146 | for (let i = 0, l = vnode.children.length; i < l; i++) {
147 | const child = vnode.children[i]
148 | if (isDef(child.tag) && (
149 | isUndef(child.ns) || (isTrue(force) && child.tag !== 'svg'))) {
150 | applyNS(child, ns, force)
151 | }
152 | }
153 | }
154 | }
155 |
156 | // ref #5318
157 | // necessary to ensure parent re-render when deep bindings like :style and
158 | // :class are used on slot nodes
159 | function registerDeepBindings(data) {
160 | if (isObject(data.style)) {
161 | traverse(data.style)
162 | }
163 | if (isObject(data.class)) {
164 | traverse(data.class)
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/codes/vue/src/core/instance/init.js:
--------------------------------------------------------------------------------
1 | import config from '../config'
2 | import {
3 | initProxy
4 | } from './proxy'
5 | import {
6 | initState
7 | } from './state'
8 | import {
9 | initRender
10 | } from './render'
11 | import {
12 | initEvents
13 | } from './events'
14 | import {
15 | mark,
16 | measure
17 | } from '../util/perf'
18 | import {
19 | initLifecycle,
20 | callHook
21 | } from './lifecycle'
22 | import {
23 | initProvide,
24 | initInjections
25 | } from './inject'
26 | import {
27 | extend,
28 | mergeOptions,
29 | formatComponentName
30 | } from '../util/index'
31 |
32 | let uid = 0
33 |
34 | export function initMixin(Vue) {
35 | Vue.prototype._init = function (options) {
36 | const vm = this
37 | // a uid
38 | vm._uid = uid++
39 |
40 | let startTag, endTag
41 | /* istanbul ignore if */
42 | if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
43 | startTag = `vue-perf-start:${vm._uid}`
44 | endTag = `vue-perf-end:${vm._uid}`
45 | mark(startTag)
46 | }
47 |
48 | // a flag to avoid this being observed
49 | vm._isVue = true
50 | // merge options
51 | if (options && options._isComponent) {
52 | // optimize internal component instantiation
53 | // since dynamic options merging is pretty slow, and none of the
54 | // internal component options needs special treatment.
55 | initInternalComponent(vm, options)
56 | } else {
57 | vm.$options = mergeOptions(
58 | resolveConstructorOptions(vm.constructor),
59 | options || {},
60 | vm
61 | )
62 | }
63 | /* istanbul ignore else */
64 | if (process.env.NODE_ENV !== 'production') {
65 | initProxy(vm)
66 | } else {
67 | vm._renderProxy = vm
68 | }
69 | // expose real self
70 | vm._self = vm
71 | initLifecycle(vm)
72 | initEvents(vm)
73 | initRender(vm)
74 | callHook(vm, 'beforeCreate')
75 | initInjections(vm) // resolve injections before data/props
76 | initState(vm)
77 | initProvide(vm) // resolve provide after data/props
78 | callHook(vm, 'created')
79 |
80 | /* istanbul ignore if */
81 | if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
82 | vm._name = formatComponentName(vm, false)
83 | mark(endTag)
84 | measure(`vue ${vm._name} init`, startTag, endTag)
85 | }
86 |
87 | if (vm.$options.el) {
88 | vm.$mount(vm.$options.el)
89 | }
90 | }
91 | }
92 |
93 | export function initInternalComponent(vm, options) {
94 | const opts = vm.$options = Object.create(vm.constructor.options)
95 | // doing this because it's faster than dynamic enumeration.
96 | const parentVnode = options._parentVnode
97 | opts.parent = options.parent
98 | opts._parentVnode = parentVnode
99 |
100 | const vnodeComponentOptions = parentVnode.componentOptions
101 | opts.propsData = vnodeComponentOptions.propsData
102 | opts._parentListeners = vnodeComponentOptions.listeners
103 | opts._renderChildren = vnodeComponentOptions.children
104 | opts._componentTag = vnodeComponentOptions.tag
105 |
106 | if (options.render) {
107 | opts.render = options.render
108 | opts.staticRenderFns = options.staticRenderFns
109 | }
110 | }
111 |
112 | export function resolveConstructorOptions(Ctor) {
113 | let options = Ctor.options
114 | if (Ctor.super) {
115 | const superOptions = resolveConstructorOptions(Ctor.super)
116 | const cachedSuperOptions = Ctor.superOptions
117 | if (superOptions !== cachedSuperOptions) {
118 | // super option changed,
119 | // need to resolve new options.
120 | Ctor.superOptions = superOptions
121 | // check if there are any late-modified/attached options (#4976)
122 | const modifiedOptions = resolveModifiedOptions(Ctor)
123 | // update base extend options
124 | if (modifiedOptions) {
125 | extend(Ctor.extendOptions, modifiedOptions)
126 | }
127 | options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
128 | if (options.name) {
129 | options.components[options.name] = Ctor
130 | }
131 | }
132 | }
133 | return options
134 | }
135 |
136 | function resolveModifiedOptions(Ctor) {
137 | let modified
138 | const latest = Ctor.options
139 | const extended = Ctor.extendOptions
140 | const sealed = Ctor.sealedOptions
141 | for (const key in latest) {
142 | if (latest[key] !== sealed[key]) {
143 | if (!modified) modified = {}
144 | modified[key] = dedupe(latest[key], extended[key], sealed[key])
145 | }
146 | }
147 | return modified
148 | }
149 |
150 | function dedupe(latest, extended, sealed) {
151 | // compare latest and sealed to ensure lifecycle hooks won't be duplicated
152 | // between merges
153 | if (Array.isArray(latest)) {
154 | const res = []
155 | sealed = Array.isArray(sealed) ? sealed : [sealed]
156 | extended = Array.isArray(extended) ? extended : [extended]
157 | for (let i = 0; i < latest.length; i++) {
158 | // push original options and not sealed options to exclude duplicated options
159 | if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) {
160 | res.push(latest[i])
161 | }
162 | }
163 | return res
164 | } else {
165 | return latest
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/codes/vue/src/core/util/props.js:
--------------------------------------------------------------------------------
1 | import {
2 | warn
3 | } from './debug'
4 | import {
5 | observe,
6 | toggleObserving,
7 | shouldObserve
8 | } from '../observer/index'
9 | import {
10 | hasOwn,
11 | isObject,
12 | toRawType,
13 | hyphenate,
14 | capitalize,
15 | isPlainObject
16 | } from '../../shared/util'
17 |
18 | export function validateProp(
19 | key,
20 | propOptions,
21 | propsData,
22 | vm
23 | ) {
24 | const prop = propOptions[key]
25 | const absent = !hasOwn(propsData, key)
26 | let value = propsData[key]
27 | // boolean casting
28 | const booleanIndex = getTypeIndex(Boolean, prop.type)
29 | if (booleanIndex > -1) {
30 | if (absent && !hasOwn(prop, 'default')) {
31 | value = false
32 | } else if (value === '' || value === hyphenate(key)) {
33 | // only cast empty string / same name to boolean if
34 | // boolean has higher priority
35 | const stringIndex = getTypeIndex(String, prop.type)
36 | if (stringIndex < 0 || booleanIndex < stringIndex) {
37 | value = true
38 | }
39 | }
40 | }
41 | // check default value
42 | if (value === undefined) {
43 | value = getPropDefaultValue(vm, prop, key)
44 | // since the default value is a fresh copy,
45 | // make sure to observe it.
46 | const prevShouldObserve = shouldObserve
47 | toggleObserving(true)
48 | observe(value)
49 | toggleObserving(prevShouldObserve)
50 | }
51 | if (
52 | process.env.NODE_ENV !== 'production' &&
53 | // skip validation for weex recycle-list child component props
54 | !(__WEEX__ && isObject(value) && ('@binding' in value))
55 | ) {
56 | assertProp(prop, key, value, vm, absent)
57 | }
58 | return value
59 | }
60 |
61 | /**
62 | * Get the default value of a prop.
63 | */
64 | function getPropDefaultValue(vm, prop, key) {
65 | // no default, return undefined
66 | if (!hasOwn(prop, 'default')) {
67 | return undefined
68 | }
69 | const def = prop.default
70 | // warn against non-factory defaults for Object & Array
71 | if (process.env.NODE_ENV !== 'production' && isObject(def)) {
72 | warn(
73 | 'Invalid default value for prop "' + key + '": ' +
74 | 'Props with type Object/Array must use a factory function ' +
75 | 'to return the default value.',
76 | vm
77 | )
78 | }
79 | // the raw prop value was also undefined from previous render,
80 | // return previous default value to avoid unnecessary watcher trigger
81 | if (vm && vm.$options.propsData &&
82 | vm.$options.propsData[key] === undefined &&
83 | vm._props[key] !== undefined
84 | ) {
85 | return vm._props[key]
86 | }
87 | // call factory function for non-Function types
88 | // a value is Function if its prototype is function even across different execution context
89 | return typeof def === 'function' && getType(prop.type) !== 'Function' ?
90 | def.call(vm) :
91 | def
92 | }
93 |
94 | /**
95 | * Assert whether a prop is valid.
96 | */
97 | function assertProp(
98 | prop,
99 | name,
100 | value,
101 | vm,
102 | absent
103 | ) {
104 | if (prop.required && absent) {
105 | warn(
106 | 'Missing required prop: "' + name + '"',
107 | vm
108 | )
109 | return
110 | }
111 | if (value == null && !prop.required) {
112 | return
113 | }
114 | let type = prop.type
115 | let valid = !type || type === true
116 | const expectedTypes = []
117 | if (type) {
118 | if (!Array.isArray(type)) {
119 | type = [type]
120 | }
121 | for (let i = 0; i < type.length && !valid; i++) {
122 | const assertedType = assertType(value, type[i])
123 | expectedTypes.push(assertedType.expectedType || '')
124 | valid = assertedType.valid
125 | }
126 | }
127 | if (!valid) {
128 | warn(
129 | `Invalid prop: type check failed for prop "${name}".` +
130 | ` Expected ${expectedTypes.map(capitalize).join(', ')}` +
131 | `, got ${toRawType(value)}.`,
132 | vm
133 | )
134 | return
135 | }
136 | const validator = prop.validator
137 | if (validator) {
138 | if (!validator(value)) {
139 | warn(
140 | 'Invalid prop: custom validator check failed for prop "' + name + '".',
141 | vm
142 | )
143 | }
144 | }
145 | }
146 |
147 | const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/
148 |
149 | function assertType(value, type)
150 |
151 |
152 | {
153 | let valid
154 | const expectedType = getType(type)
155 | if (simpleCheckRE.test(expectedType)) {
156 | const t = typeof value
157 | valid = t === expectedType.toLowerCase()
158 | // for primitive wrapper objects
159 | if (!valid && t === 'object') {
160 | valid = value instanceof type
161 | }
162 | } else if (expectedType === 'Object') {
163 | valid = isPlainObject(value)
164 | } else if (expectedType === 'Array') {
165 | valid = Array.isArray(value)
166 | } else {
167 | valid = value instanceof type
168 | }
169 | return {
170 | valid,
171 | expectedType
172 | }
173 | }
174 |
175 | /**
176 | * Use function string name to check built-in types,
177 | * because a simple equality check will fail when running
178 | * across different vms / iframes.
179 | */
180 | function getType(fn) {
181 | const match = fn && fn.toString().match(/^\s*function (\w+)/)
182 | return match ? match[1] : ''
183 | }
184 |
185 | function isSameType(a, b) {
186 | return getType(a) === getType(b)
187 | }
188 |
189 | function getTypeIndex(type, expectedTypes) {
190 | if (!Array.isArray(expectedTypes)) {
191 | return isSameType(expectedTypes, type) ? 0 : -1
192 | }
193 | for (let i = 0, len = expectedTypes.length; i < len; i++) {
194 | if (isSameType(expectedTypes[i], type)) {
195 | return i
196 | }
197 | }
198 | return -1
199 | }
200 |
--------------------------------------------------------------------------------
/codes/vue/src/shared/util.js:
--------------------------------------------------------------------------------
1 | export const emptyObject = Object.freeze({})
2 |
3 | // these helpers produces better vm code in JS engines due to their
4 | // explicitness and function inlining
5 | export function isUndef(v) {
6 | return v === undefined || v === null
7 | }
8 |
9 | export function isDef(v) {
10 | return v !== undefined && v !== null
11 | }
12 |
13 | export function isTrue(v) {
14 | return v === true
15 | }
16 |
17 | export function isFalse(v) {
18 | return v === false
19 | }
20 |
21 | /**
22 | * Check if value is primitive
23 | */
24 | export function isPrimitive(value) {
25 | return (
26 | typeof value === 'string' ||
27 | typeof value === 'number' ||
28 | typeof value === 'symbol' ||
29 | typeof value === 'boolean'
30 | )
31 | }
32 |
33 | /**
34 | * Quick object check - this is primarily used to tell
35 | * Objects from primitive values when we know the value
36 | * is a JSON-compliant type.
37 | */
38 | export function isObject(obj) {
39 | return obj !== null && typeof obj === 'object'
40 | }
41 |
42 | /**
43 | * Get the raw type string of a value e.g. [object Object]
44 | */
45 | const _toString = Object.prototype.toString
46 |
47 | export function toRawType(value) {
48 | return _toString.call(value).slice(8, -1)
49 | }
50 |
51 | /**
52 | * Strict object type check. Only returns true
53 | * for plain JavaScript objects.
54 | */
55 | export function isPlainObject(obj) {
56 | return _toString.call(obj) === '[object Object]'
57 | }
58 |
59 | export function isRegExp(v) {
60 | return _toString.call(v) === '[object RegExp]'
61 | }
62 |
63 | /**
64 | * Check if val is a valid array index.
65 | */
66 | export function isValidArrayIndex(val) {
67 | const n = parseFloat(String(val))
68 | return n >= 0 && Math.floor(n) === n && isFinite(val)
69 | }
70 |
71 | /**
72 | * Convert a value to a string that is actually rendered.
73 | */
74 | export function toString(val) {
75 | return val == null ?
76 | '' :
77 | typeof val === 'object' ?
78 | JSON.stringify(val, null, 2) :
79 | String(val)
80 | }
81 |
82 | /**
83 | * Convert a input value to a number for persistence.
84 | * If the conversion fails, return original string.
85 | */
86 | export function toNumber(val) {
87 | const n = parseFloat(val)
88 | return isNaN(n) ? val : n
89 | }
90 |
91 | /**
92 | * Make a map and return a function for checking if a key
93 | * is in that map.
94 | */
95 | export function makeMap(
96 | str,
97 | expectsLowerCase
98 | ) {
99 | const map = Object.create(null)
100 | const list = str.split(',')
101 | for (let i = 0; i < list.length; i++) {
102 | map[list[i]] = true
103 | }
104 | return expectsLowerCase ?
105 | val => map[val.toLowerCase()] :
106 | val => map[val]
107 | }
108 |
109 | /**
110 | * Check if a tag is a built-in tag.
111 | */
112 | export const isBuiltInTag = makeMap('slot,component', true)
113 |
114 | /**
115 | * Check if a attribute is a reserved attribute.
116 | */
117 | export const isReservedAttribute = makeMap('key,ref,slot,slot-scope,is')
118 |
119 | /**
120 | * Remove an item from an array
121 | */
122 | export function remove(arr, item) {
123 | if (arr.length) {
124 | const index = arr.indexOf(item)
125 | if (index > -1) {
126 | return arr.splice(index, 1)
127 | }
128 | }
129 | }
130 |
131 | /**
132 | * Check whether the object has the property.
133 | */
134 | const hasOwnProperty = Object.prototype.hasOwnProperty
135 | export function hasOwn(obj, key) {
136 | return hasOwnProperty.call(obj, key)
137 | }
138 |
139 | /**
140 | * Create a cached version of a pure function.
141 | */
142 | export function cached(fn) {
143 | const cache = Object.create(null)
144 | return (function cachedFn(str) {
145 | const hit = cache[str]
146 | return hit || (cache[str] = fn(str))
147 | })
148 | }
149 |
150 | /**
151 | * Camelize a hyphen-delimited string.
152 | */
153 | const camelizeRE = /-(\w)/g
154 | export const camelize = cached((str) => {
155 | return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
156 | })
157 |
158 | /**
159 | * Capitalize a string.
160 | */
161 | export const capitalize = cached((str) => {
162 | return str.charAt(0).toUpperCase() + str.slice(1)
163 | })
164 |
165 | /**
166 | * Hyphenate a camelCase string.
167 | */
168 | const hyphenateRE = /\B([A-Z])/g
169 | export const hyphenate = cached((str) => {
170 | return str.replace(hyphenateRE, '-$1').toLowerCase()
171 | })
172 |
173 | /**
174 | * Simple bind polyfill for environments that do not support it... e.g.
175 | * PhantomJS 1.x. Technically we don't need this anymore since native bind is
176 | * now more performant in most browsers, but removing it would be breaking for
177 | * code that was able to run in PhantomJS 1.x, so this must be kept for
178 | * backwards compatibility.
179 | */
180 |
181 | /* istanbul ignore next */
182 | function polyfillBind(fn, ctx) {
183 | function boundFn(a) {
184 | const l = arguments.length
185 | return l ?
186 | l > 1 ?
187 | fn.apply(ctx, arguments) :
188 | fn.call(ctx, a) :
189 | fn.call(ctx)
190 | }
191 |
192 | boundFn._length = fn.length
193 | return boundFn
194 | }
195 |
196 | function nativeBind(fn, ctx) {
197 | return fn.bind(ctx)
198 | }
199 |
200 | export const bind = Function.prototype.bind ?
201 | nativeBind :
202 | polyfillBind
203 |
204 | /**
205 | * Convert an Array-like object to a real Array.
206 | */
207 | export function toArray(list, start) {
208 | start = start || 0
209 | let i = list.length - start
210 | const ret = new Array(i)
211 | while (i--) {
212 | ret[i] = list[i + start]
213 | }
214 | return ret
215 | }
216 |
217 | /**
218 | * Mix properties into target object.
219 | */
220 | export function extend(to, _from) {
221 | for (const key in _from) {
222 | to[key] = _from[key]
223 | }
224 | return to
225 | }
226 |
227 | /**
228 | * Merge an Array of Objects into a single Object.
229 | */
230 | export function toObject(arr) {
231 | const res = {}
232 | for (let i = 0; i < arr.length; i++) {
233 | if (arr[i]) {
234 | extend(res, arr[i])
235 | }
236 | }
237 | return res
238 | }
239 |
240 | /**
241 | * Perform no operation.
242 | * Stubbing args to make Flow happy without leaving useless transpiled code
243 | * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/)
244 | */
245 | export function noop(a, b, c) {}
246 |
247 | /**
248 | * Always return false.
249 | */
250 | export const no = (a, b, c) => false
251 |
252 | /**
253 | * Return same value
254 | */
255 | export const identity = (_) => _
256 |
257 | /**
258 | * Generate a static keys string from compiler modules.
259 | */
260 | export function genStaticKeys(modules) {
261 | return modules.reduce((keys, m) => {
262 | return keys.concat(m.staticKeys || [])
263 | }, []).join(',')
264 | }
265 |
266 | /**
267 | * Check if two values are loosely equal - that is,
268 | * if they are plain objects, do they have the same shape?
269 | */
270 | export function looseEqual(a, b) {
271 | if (a === b) return true
272 | const isObjectA = isObject(a)
273 | const isObjectB = isObject(b)
274 | if (isObjectA && isObjectB) {
275 | try {
276 | const isArrayA = Array.isArray(a)
277 | const isArrayB = Array.isArray(b)
278 | if (isArrayA && isArrayB) {
279 | return a.length === b.length && a.every((e, i) => {
280 | return looseEqual(e, b[i])
281 | })
282 | } else if (!isArrayA && !isArrayB) {
283 | const keysA = Object.keys(a)
284 | const keysB = Object.keys(b)
285 | return keysA.length === keysB.length && keysA.every(key => {
286 | return looseEqual(a[key], b[key])
287 | })
288 | } else {
289 | /* istanbul ignore next */
290 | return false
291 | }
292 | } catch (e) {
293 | /* istanbul ignore next */
294 | return false
295 | }
296 | } else if (!isObjectA && !isObjectB) {
297 | return String(a) === String(b)
298 | } else {
299 | return false
300 | }
301 | }
302 |
303 | export function looseIndexOf(arr, val) {
304 | for (let i = 0; i < arr.length; i++) {
305 | if (looseEqual(arr[i], val)) return i
306 | }
307 | return -1
308 | }
309 |
310 | /**
311 | * Ensure a function is called only once.
312 | */
313 | export function once(fn) {
314 | let called = false
315 | return function () {
316 | if (!called) {
317 | called = true
318 | fn.apply(this, arguments)
319 | }
320 | }
321 | }
322 |
--------------------------------------------------------------------------------
/codes/vue/src/core/observer/watcher.js:
--------------------------------------------------------------------------------
1 | import {
2 | warn,
3 | remove,
4 | isObject,
5 | parsePath,
6 | _Set as Set,
7 | handleError
8 | } from '../util/index'
9 |
10 | import {
11 | traverse
12 | } from './traverse'
13 | import {
14 | queueWatcher
15 | } from './scheduler'
16 | import Dep, {
17 | pushTarget,
18 | popTarget
19 | } from './dep'
20 |
21 | let uid = 0
22 |
23 | /**
24 | * A watcher parses an expression, collects dependencies,
25 | * and fires callback when the expression value changes.
26 | * This is used for both the $watch() api and directives.
27 | */
28 | export default class Watcher {
29 | constructor(
30 | vm,
31 | expOrFn,
32 | cb,
33 | options,
34 | isRenderWatcher
35 | ) {
36 | this.vm = vm
37 | if (isRenderWatcher) {
38 | vm._watcher = this
39 | }
40 | vm._watchers.push(this)
41 | // options
42 | if (options) {
43 | this.deep = !!options.deep
44 | this.user = !!options.user
45 | this.computed = !!options.computed
46 | this.sync = !!options.sync
47 | this.before = options.before
48 | } else {
49 | this.deep = this.user = this.computed = this.sync = false
50 | }
51 | this.cb = cb
52 | this.id = ++uid // uid for batching
53 | this.active = true
54 | this.dirty = this.computed // for computed watchers
55 | // 初始化deps相关属性
56 | this.deps = []
57 | this.newDeps = []
58 | this.depIds = new Set()
59 | this.newDepIds = new Set()
60 | this.expression = process.env.NODE_ENV !== 'production'
61 | ? expOrFn.toString()
62 | : ''
63 | // parse expression for getter
64 | // getter 是一个函数,接受 vm 作为参数,返回值
65 | if (typeof expOrFn === 'function') {
66 | this.getter = expOrFn
67 | } else {
68 | this.getter = parsePath(expOrFn)
69 | if (!this.getter) {
70 | this.getter = function () {}
71 | process.env.NODE_ENV !== 'production' && warn(
72 | `Failed watching path: "${expOrFn}" ` +
73 | 'Watcher only accepts simple dot-delimited paths. ' +
74 | 'For full control, use a function instead.',
75 | vm
76 | )
77 | }
78 | }
79 | // 对 computed 特殊处理,添加一个 dep 让 watcher 可以被依赖
80 | if (this.computed) {
81 | this.value = undefined
82 | this.dep = new Dep()
83 | }
84 | // 如果不是computed,初始化 this.value 的值:
85 | // 1. 通过调用 getter 获取值 value
86 | // 2. 如果是 deep 依赖,递归访问 value 的所有属性,收集依赖
87 | else {
88 | this.value = this.get()
89 | }
90 | }
91 |
92 | /**
93 | * Evaluate the getter, and re-collect dependencies.
94 | * 执行 getter,并收集依赖
95 | */
96 | get() {
97 | // 设定 Dep.target 为 this(当前 watcher ),
98 | // 使得 getter 调用和 traverse 调用时当前 watcher 被正确指定为 target。
99 | pushTarget(this)
100 | let value
101 | const vm = this.vm
102 | try {
103 | // ⚠️
104 | // 重点注意,getter执行时,数据的getter会被调用(数据已被observe处理过),
105 | // 这意味着所依赖的数据对应的依赖已经被收集(非deep,如 data.user 代表 user 已被收集,
106 | // 但 user.name 不在依赖列表里,需要通过下面的 traverse 收集)。
107 | value = this.getter.call(vm, vm)
108 | } catch (e) {
109 | if (this.user) {
110 | handleError(e, vm, `getter for watcher "${this.expression}"`)
111 | } else {
112 | throw e
113 | }
114 | } finally {
115 | // "touch" every property so they are all tracked as
116 | // dependencies for deep watching
117 | if (this.deep) {
118 | traverse(value)
119 | }
120 | // 重置Dep.target
121 | popTarget()
122 | this.cleanupDeps()
123 | }
124 | return value
125 | }
126 |
127 | /**
128 | * Add a dependency to this directive.
129 | * 被 dep.depend 调用,把 dep 加到 watcher 的 deps 里。
130 | */
131 | addDep(dep) {
132 | const id = dep.id
133 | // 如果在新一轮依赖收集里没被记录,则记录;
134 | if (!this.newDepIds.has(id)) {
135 | this.newDepIds.add(id)
136 | this.newDeps.push(dep)
137 | // 如果不在原来的 deps 里,则需要调用 dep.addSub,
138 | // 把 watcher 添加到 dep 的 subs 里。
139 | if (!this.depIds.has(id)) {
140 | dep.addSub(this)
141 | }
142 | }
143 | }
144 |
145 | /**
146 | * Clean up for dependency collection.
147 | * 清理 wathcer 的依赖:
148 | * - 更新 deps/depIds;
149 | * - 清空 newDeps 和 newDepIds,为下次依赖收集做好准备
150 | */
151 | cleanupDeps() {
152 | // 如果原来的 dep 已经不在新 dep 里(说明不再是依赖了),
153 | // 则用 dep.removeSub 来把当前 watcher 从订阅者列表中清除。
154 | let i = this.deps.length
155 | while (i--) {
156 | const dep = this.deps[i]
157 | if (!this.newDepIds.has(dep.id)) {
158 | dep.removeSub(this)
159 | }
160 | }
161 | // 把 newDepIds 设置为 depIds,把 newDeps 设置为 deps,
162 | // 并清空 this.newDepIds/this.newDeps ,为下次依赖收集做好准备。
163 | // 这里比较有意思的是用 tmp 作为中间变量,复用了 deps/newDeps,depIds/newDepIds,
164 | // 提高性能(防止过多GC)。
165 | let tmp = this.depIds
166 | this.depIds = this.newDepIds
167 | this.newDepIds = tmp
168 | this.newDepIds.clear()
169 | tmp = this.deps
170 | this.deps = this.newDeps
171 | this.newDeps = tmp
172 | this.newDeps.length = 0
173 | }
174 |
175 | /**
176 | * Subscriber interface.
177 | * Will be called when a dependency changes.
178 | * dep.notify 通知 watcher,watcher.update 被执行。
179 | */
180 | update() {
181 | /* istanbul ignore else */
182 | if (this.computed) {
183 | // A computed property watcher has two modes: lazy and activated.
184 | // It initializes as lazy by default, and only becomes activated when
185 | // it is depended on by at least one subscriber, which is typically
186 | // another computed property or a component's render function.
187 | if (this.dep.subs.length === 0) {
188 | // In lazy mode, we don't want to perform computations until necessary,
189 | // so we simply mark the watcher as dirty. The actual computation is
190 | // performed just-in-time in this.evaluate() when the computed property
191 | // is accessed.
192 | this.dirty = true
193 | } else {
194 | // In activated mode, we want to proactively perform the computation
195 | // but only notify our subscribers when the value has indeed changed.
196 | this.getAndInvoke(() => {
197 | this.dep.notify()
198 | })
199 | }
200 | } else if (this.sync) {
201 | this.run()
202 | } else {
203 | queueWatcher(this)
204 | }
205 | }
206 |
207 | /**
208 | * Scheduler job interface.
209 | * Will be called by the scheduler.
210 | */
211 | run() {
212 | if (this.active) {
213 | this.getAndInvoke(this.cb)
214 | }
215 | }
216 |
217 | // 执行回调,更新 this.value
218 | getAndInvoke(cb) {
219 | const value = this.get()
220 | if (
221 | value !== this.value ||
222 | // Deep watchers and watchers on Object/Arrays should fire even
223 | // when the value is the same, because the value may
224 | // have mutated.
225 | isObject(value) ||
226 | this.deep
227 | ) {
228 | // set new value
229 | const oldValue = this.value
230 | this.value = value
231 | this.dirty = false
232 | if (this.user) {
233 | try {
234 | cb.call(this.vm, value, oldValue)
235 | } catch (e) {
236 | handleError(e, this.vm, `callback for watcher "${this.expression}"`)
237 | }
238 | } else {
239 | cb.call(this.vm, value, oldValue)
240 | }
241 | }
242 | }
243 |
244 | /**
245 | * Evaluate and return the value of the watcher.
246 | * This only gets called for computed property watchers.
247 | */
248 | evaluate() {
249 | if (this.dirty) {
250 | this.value = this.get()
251 | this.dirty = false
252 | }
253 | return this.value
254 | }
255 |
256 | /**
257 | * Depend on this watcher. Only for computed property watchers.
258 | */
259 | depend() {
260 | if (this.dep && Dep.target) {
261 | this.dep.depend()
262 | }
263 | }
264 |
265 | /**
266 | * Remove self from all dependencies' subscriber list.
267 | */
268 | teardown() {
269 | if (this.active) {
270 | // remove self from vm's watcher list
271 | // this is a somewhat expensive operation so we skip it
272 | // if the vm is being destroyed.
273 | if (!this.vm._isBeingDestroyed) {
274 | remove(this.vm._watchers, this)
275 | }
276 | let i = this.deps.length
277 | while (i--) {
278 | this.deps[i].removeSub(this)
279 | }
280 | this.active = false
281 | }
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/codes/vue/src/core/vdom/create-component.js:
--------------------------------------------------------------------------------
1 | import VNode from './vnode'
2 | import {
3 | resolveConstructorOptions
4 | } from 'core/instance/init'
5 | import {
6 | queueActivatedComponent
7 | } from 'core/observer/scheduler'
8 | import {
9 | createFunctionalComponent
10 | } from './create-functional-component'
11 |
12 | import {
13 | warn,
14 | isDef,
15 | isUndef,
16 | isTrue,
17 | isObject
18 | } from '../util/index'
19 |
20 | import {
21 | resolveAsyncComponent,
22 | createAsyncPlaceholder,
23 | extractPropsFromVNodeData
24 | } from './helpers/index'
25 |
26 | import {
27 | callHook,
28 | activeInstance,
29 | updateChildComponent,
30 | activateChildComponent,
31 | deactivateChildComponent
32 | } from '../instance/lifecycle'
33 |
34 | import {
35 | isRecyclableComponent,
36 | renderRecyclableComponentTemplate
37 | } from 'weex/runtime/recycle-list/render-component-template'
38 |
39 | // inline hooks to be invoked on component VNodes during patch
40 | const componentVNodeHooks = {
41 | init(vnode, hydrating) {
42 | if (
43 | vnode.componentInstance &&
44 | !vnode.componentInstance._isDestroyed &&
45 | vnode.data.keepAlive
46 | ) {
47 | // kept-alive components, treat as a patch
48 | const mountedNode = vnode // work around flow
49 | componentVNodeHooks.prepatch(mountedNode, mountedNode)
50 | } else {
51 | const child = vnode.componentInstance = createComponentInstanceForVnode(
52 | vnode,
53 | activeInstance
54 | )
55 | child.$mount(hydrating ? vnode.elm : undefined, hydrating)
56 | }
57 | },
58 |
59 | prepatch(oldVnode, vnode) {
60 | const options = vnode.componentOptions
61 | const child = vnode.componentInstance = oldVnode.componentInstance
62 | updateChildComponent(
63 | child,
64 | options.propsData, // updated props
65 | options.listeners, // updated listeners
66 | vnode, // new parent vnode
67 | options.children // new children
68 | )
69 | },
70 |
71 | insert(vnode) {
72 | const {
73 | context,
74 | componentInstance
75 | } = vnode
76 | if (!componentInstance._isMounted) {
77 | componentInstance._isMounted = true
78 | callHook(componentInstance, 'mounted')
79 | }
80 | if (vnode.data.keepAlive) {
81 | if (context._isMounted) {
82 | // vue-router#1212
83 | // During updates, a kept-alive component's child components may
84 | // change, so directly walking the tree here may call activated hooks
85 | // on incorrect children. Instead we push them into a queue which will
86 | // be processed after the whole patch process ended.
87 | queueActivatedComponent(componentInstance)
88 | } else {
89 | activateChildComponent(componentInstance, true /* direct */ )
90 | }
91 | }
92 | },
93 |
94 | destroy(vnode) {
95 | const {
96 | componentInstance
97 | } = vnode
98 | if (!componentInstance._isDestroyed) {
99 | if (!vnode.data.keepAlive) {
100 | componentInstance.$destroy()
101 | } else {
102 | deactivateChildComponent(componentInstance, true /* direct */ )
103 | }
104 | }
105 | }
106 | }
107 |
108 | const hooksToMerge = Object.keys(componentVNodeHooks)
109 |
110 | export function createComponent(
111 | Ctor,
112 | data,
113 | context,
114 | children,
115 | tag
116 | ) {
117 | if (isUndef(Ctor)) {
118 | return
119 | }
120 |
121 | const baseCtor = context.$options._base
122 |
123 | // plain options object: turn it into a constructor
124 | if (isObject(Ctor)) {
125 | Ctor = baseCtor.extend(Ctor)
126 | }
127 |
128 | // if at this stage it's not a constructor or an async component factory,
129 | // reject.
130 | if (typeof Ctor !== 'function') {
131 | if (process.env.NODE_ENV !== 'production') {
132 | warn(`Invalid Component definition: ${String(Ctor)}`, context)
133 | }
134 | return
135 | }
136 |
137 | // async component
138 | let asyncFactory
139 | if (isUndef(Ctor.cid)) {
140 | asyncFactory = Ctor
141 | Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
142 | if (Ctor === undefined) {
143 | // return a placeholder node for async component, which is rendered
144 | // as a comment node but preserves all the raw information for the node.
145 | // the information will be used for async server-rendering and hydration.
146 | return createAsyncPlaceholder(
147 | asyncFactory,
148 | data,
149 | context,
150 | children,
151 | tag
152 | )
153 | }
154 | }
155 |
156 | data = data || {}
157 |
158 | // resolve constructor options in case global mixins are applied after
159 | // component constructor creation
160 | resolveConstructorOptions(Ctor)
161 |
162 | // transform component v-model data into props & events
163 | if (isDef(data.model)) {
164 | transformModel(Ctor.options, data)
165 | }
166 |
167 | // extract props
168 | const propsData = extractPropsFromVNodeData(data, Ctor, tag)
169 |
170 | // functional component
171 | if (isTrue(Ctor.options.functional)) {
172 | return createFunctionalComponent(Ctor, propsData, data, context, children)
173 | }
174 |
175 | // extract listeners, since these needs to be treated as
176 | // child component listeners instead of DOM listeners
177 | const listeners = data.on
178 | // replace with listeners with .native modifier
179 | // so it gets processed during parent component patch.
180 | data.on = data.nativeOn
181 |
182 | if (isTrue(Ctor.options.abstract)) {
183 | // abstract components do not keep anything
184 | // other than props & listeners & slot
185 |
186 | // work around flow
187 | const slot = data.slot
188 | data = {}
189 | if (slot) {
190 | data.slot = slot
191 | }
192 | }
193 |
194 | // install component management hooks onto the placeholder node
195 | installComponentHooks(data)
196 |
197 | // return a placeholder vnode
198 | const name = Ctor.options.name || tag
199 | const vnode = new VNode(
200 | `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
201 | data, undefined, undefined, undefined, context, {
202 | Ctor,
203 | propsData,
204 | listeners,
205 | tag,
206 | children
207 | },
208 | asyncFactory
209 | )
210 |
211 | // Weex specific: invoke recycle-list optimized @render function for
212 | // extracting cell-slot template.
213 | // https://github.com/Hanks10100/weex-native-directive/tree/master/component
214 | /* istanbul ignore if */
215 | if (__WEEX__ && isRecyclableComponent(vnode)) {
216 | return renderRecyclableComponentTemplate(vnode)
217 | }
218 |
219 | return vnode
220 | }
221 |
222 | export function createComponentInstanceForVnode(
223 | vnode, // we know it's MountedComponentVNode but flow doesn't
224 | parent, // activeInstance in lifecycle state
225 | ) {
226 | const options = {
227 | _isComponent: true,
228 | _parentVnode: vnode,
229 | parent
230 | }
231 | // check inline-template render functions
232 | const inlineTemplate = vnode.data.inlineTemplate
233 | if (isDef(inlineTemplate)) {
234 | options.render = inlineTemplate.render
235 | options.staticRenderFns = inlineTemplate.staticRenderFns
236 | }
237 | return new vnode.componentOptions.Ctor(options)
238 | }
239 |
240 | function installComponentHooks(data) {
241 | const hooks = data.hook || (data.hook = {})
242 | for (let i = 0; i < hooksToMerge.length; i++) {
243 | const key = hooksToMerge[i]
244 | const existing = hooks[key]
245 | const toMerge = componentVNodeHooks[key]
246 | if (existing !== toMerge && !(existing && existing._merged)) {
247 | hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
248 | }
249 | }
250 | }
251 |
252 | function mergeHook(f1, f2) {
253 | const merged = (a, b) => {
254 | // flow complains about extra args which is why we use any
255 | f1(a, b)
256 | f2(a, b)
257 | }
258 | merged._merged = true
259 | return merged
260 | }
261 |
262 | // transform component v-model info (value and callback) into
263 | // prop and event handler respectively.
264 | function transformModel(options, data) {
265 | const prop = (options.model && options.model.prop) || 'value'
266 | const event = (options.model && options.model.event) || 'input';
267 | (data.props || (data.props = {}))[prop] = data.model.value
268 | const on = data.on || (data.on = {})
269 | if (isDef(on[event])) {
270 | on[event] = [data.model.callback].concat(on[event])
271 | } else {
272 | on[event] = data.model.callback
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/codes/vue/src/core/observer/index.js:
--------------------------------------------------------------------------------
1 | import Dep from './dep'
2 | import VNode from '../vdom/vnode'
3 | import {
4 | arrayMethods
5 | } from './array'
6 | import {
7 | def,
8 | warn,
9 | hasOwn,
10 | hasProto,
11 | isObject,
12 | isPlainObject,
13 | isPrimitive,
14 | isUndef,
15 | isValidArrayIndex,
16 | isServerRendering
17 | } from '../util/index'
18 |
19 | const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
20 |
21 | /**
22 | * In some cases we may want to disable observation inside a component's
23 | * update computation.
24 | */
25 | export let shouldObserve = true
26 |
27 | export function toggleObserving(value) {
28 | shouldObserve = value
29 | }
30 |
31 | /**
32 | * Observer class that is attached to each observed
33 | * object. Once attached, the observer converts the target
34 | * object's property keys into getter/setters that
35 | * collect dependencies and dispatch updates.
36 | * 转化value的属性为getter / setter, 并在里面实现收集依赖和转发更新
37 | */
38 | export class Observer {
39 | // number of vms that has this object as root $data
40 | constructor(value) {
41 | this.value = value
42 | // 有什么用?
43 | // 因为this.walk(value)其实只能把value的每个属性转化为getter/setter,
44 | // 那么value本身被直接赋值(整个替换)时,怎么通知更新?
45 | // 所以有个observer实例本身的dep。
46 | // 另外,我们注意到,Vue通过`data(){}`返回的数据作为rootData,它是不能直接被
47 | // 覆盖的,我们只能设置它的属性,所以第一层的 dep 不会被用到,这个 dep 怎么
48 | // 使用的,我们看下面的 defineReactive 里的 childOb.dep.depend。
49 | this.dep = new Dep()
50 | this.vmCount = 0
51 | // 把自身设置到 value 的 __ob__ 属性上(不可枚举)
52 | def(value, '__ob__', this)
53 | // 重写 value 的每个属性为 getter/setter,
54 | // 根据是不是 array 用不同的方式。
55 | if (Array.isArray(value)) {
56 | const augment = hasProto ?
57 | protoAugment :
58 | copyAugment
59 | // 特殊处理数组的 push/shift 等等方法,使之可以监测数据变化:
60 | // 调用value的数组方法时,会调用 value.__ob__.dep.notify()
61 | augment(value, arrayMethods, arrayKeys)
62 | this.observeArray(value)
63 | } else {
64 | this.walk(value)
65 | }
66 | }
67 |
68 | /**
69 | * Walk through each property and convert them into
70 | * getter/setters. This method should only be called when
71 | * value type is Object.
72 | */
73 | walk(obj) {
74 | const keys = Object.keys(obj)
75 | for (let i = 0; i < keys.length; i++) {
76 | defineReactive(obj, keys[i])
77 | }
78 | }
79 |
80 | /**
81 | * Observe a list of Array items.
82 | */
83 | observeArray(items) {
84 | for (let i = 0, l = items.length; i < l; i++) {
85 | observe(items[i])
86 | }
87 | }
88 | }
89 |
90 | // helpers
91 |
92 | /**
93 | * Augment an target Object or Array by intercepting
94 | * the prototype chain using __proto__
95 | */
96 | function protoAugment(target, src, keys) {
97 | /* eslint-disable no-proto */
98 | target.__proto__ = src
99 | /* eslint-enable no-proto */
100 | }
101 |
102 | /**
103 | * Augment an target Object or Array by defining
104 | * hidden properties.
105 | */
106 | /* istanbul ignore next */
107 | function copyAugment(target, src, keys) {
108 | for (let i = 0, l = keys.length; i < l; i++) {
109 | const key = keys[i]
110 | def(target, key, src[key])
111 | }
112 | }
113 |
114 | /**
115 | * Attempt to create an observer instance for a value,
116 | * returns the new observer if successfully observed,
117 | * or the existing observer if the value already has one.
118 | * 为value创建对应的observer,或者返回已有的observer。
119 | */
120 | export function observe(value, asRootData) {
121 | if (!isObject(value) || value instanceof VNode) {
122 | return
123 | }
124 | let ob
125 | if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
126 | ob = value.__ob__
127 | } else if (
128 | shouldObserve &&
129 | !isServerRendering() &&
130 | (Array.isArray(value) || isPlainObject(value)) &&
131 | Object.isExtensible(value) &&
132 | !value._isVue
133 | ) {
134 | ob = new Observer(value)
135 | }
136 | if (asRootData && ob) {
137 | ob.vmCount++
138 | }
139 | return ob
140 | }
141 |
142 | /**
143 | * Define a reactive property on an Object.
144 | * 把 property 转化为 getter 和 setter
145 | * - 创建dep(dep是一个连接器,getter时收集依赖,setter时通知更新);
146 | * - 在 getter 里面进行依赖收集,在非shallow时递归收集
147 | * - 在 setter 里面进行更新通知,在非shallow时重新创建childOb
148 | */
149 | export function defineReactive(
150 | obj,
151 | key,
152 | val,
153 | customSetter,
154 | shallow
155 | ) {
156 | const dep = new Dep()
157 |
158 | const property = Object.getOwnPropertyDescriptor(obj, key)
159 | if (property && property.configurable === false) {
160 | return
161 | }
162 |
163 | // cater for pre-defined getter/setters
164 | const getter = property && property.get
165 | const setter = property && property.set
166 | if ((!getter || setter) && arguments.length === 2) {
167 | val = obj[key]
168 | }
169 |
170 | // ⚠️
171 | // 大部分情况shallow默认是false的,即默认递归observe。
172 | // - 当val是数组时,childOb被用来向当前watcher收集依赖
173 | // - 当val是普通对象时,set/del函数也会用childOb来通知val的属性添加/删除
174 | let childOb = !shallow && observe(val)
175 | Object.defineProperty(obj, key, {
176 | enumerable: true,
177 | configurable: true,
178 | // watcher 初始化时调用自己的 watcher.get(),最终调用这个 getter,
179 | // 而 dep.depend 执行,把 watcher 放到了自己的 subs 里;所以当
180 | // set 执行时,watcher 被通知更新。
181 | get: function reactiveGetter() {
182 | const value = getter ? getter.call(obj) : val
183 | if (Dep.target) {
184 | dep.depend()
185 | // ⚠️ 现在问题是为什么要依赖 childOb 呢?
186 | // (1)考虑到如果 value 是数组,那么 value 的 push/shift 之类的操作,
187 | // 是触发不了下面的 setter 的,即 dep.depend 在这种情况不会被调用。
188 | // 此时,childOb 即value这个数组对应的 ob,数组的操作会通知到childOb,
189 | // 所以可以替代 dep 来通知 watcher。
190 | // (2)Vue.set/Vue.del API(即下面的set/del函数)中,如果直接把一个对象
191 | // 设置成其它值,或者删除某个对象的key,这个时候也依赖 childOb 来通知。
192 | if (childOb) {
193 | childOb.dep.depend()
194 | // 同时,对数组元素的操作,需要通过 dependArray(value) 来建立依赖。
195 | if (Array.isArray(value)) {
196 | dependArray(value)
197 | }
198 | }
199 | }
200 | return value
201 | },
202 | set: function reactiveSetter(newVal) {
203 | const value = getter ? getter.call(obj) : val
204 | /* eslint-disable no-self-compare */
205 | if (newVal === value || (newVal !== newVal && value !== value)) {
206 | return
207 | }
208 | /* eslint-enable no-self-compare */
209 | if (process.env.NODE_ENV !== 'production' && customSetter) {
210 | customSetter()
211 | }
212 | if (setter) {
213 | setter.call(obj, newVal)
214 | } else {
215 | val = newVal
216 | }
217 | // 对新val重新创建childOb
218 | childOb = !shallow && observe(newVal)
219 | // 通知更新
220 | dep.notify()
221 | }
222 | })
223 | }
224 |
225 | /**
226 | * Set a property on an object. Adds the new property and
227 | * triggers change notification if the property doesn't
228 | * already exist.
229 | */
230 | export function set(target, key, val) {
231 | if (process.env.NODE_ENV !== 'production' &&
232 | (isUndef(target) || isPrimitive(target))
233 | ) {
234 | warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target )}`)
235 | }
236 | if (Array.isArray(target) && isValidArrayIndex(key)) {
237 | target.length = Math.max(target.length, key)
238 | target.splice(key, 1, val)
239 | return val
240 | }
241 | if (key in target && !(key in Object.prototype)) {
242 | target[key] = val
243 | return val
244 | }
245 | const ob = (target).__ob__
246 | if (target._isVue || (ob && ob.vmCount)) {
247 | process.env.NODE_ENV !== 'production' && warn(
248 | 'Avoid adding reactive properties to a Vue instance or its root $data ' +
249 | 'at runtime - declare it upfront in the data option.'
250 | )
251 | return val
252 | }
253 | if (!ob) {
254 | target[key] = val
255 | return val
256 | }
257 | defineReactive(ob.value, key, val)
258 | ob.dep.notify()
259 | return val
260 | }
261 |
262 | /**
263 | * Delete a property and trigger change if necessary.
264 | */
265 | export function del(target, key) {
266 | if (process.env.NODE_ENV !== 'production' &&
267 | (isUndef(target) || isPrimitive(target))
268 | ) {
269 | warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target )}`)
270 | }
271 | if (Array.isArray(target) && isValidArrayIndex(key)) {
272 | target.splice(key, 1)
273 | return
274 | }
275 | const ob = (target).__ob__
276 | if (target._isVue || (ob && ob.vmCount)) {
277 | process.env.NODE_ENV !== 'production' && warn(
278 | 'Avoid deleting properties on a Vue instance or its root $data ' +
279 | '- just set it to null.'
280 | )
281 | return
282 | }
283 | if (!hasOwn(target, key)) {
284 | return
285 | }
286 | delete target[key]
287 | if (!ob) {
288 | return
289 | }
290 | ob.dep.notify()
291 | }
292 |
293 | /**
294 | * Collect dependencies on array elements when the array is touched, since
295 | * we cannot intercept array element access like property getters.
296 | */
297 | function dependArray(value) {
298 | for (let e, i = 0, l = value.length; i < l; i++) {
299 | e = value[i]
300 | e && e.__ob__ && e.__ob__.dep.depend()
301 | if (Array.isArray(e)) {
302 | dependArray(e)
303 | }
304 | }
305 | }
306 |
--------------------------------------------------------------------------------
/codes/vue/src/core/observer/readme.md:
--------------------------------------------------------------------------------
1 | > 对一个数据(典型的即Plain Object),要想实现数据变化监听,一般有3种方式:
2 | > - 脏检查,如`angular.js`;
3 | > - 只能用特定方法去更新数据,如`react`的`setState`;
4 | > - getter/setter(`Object.defineProperty`),即`Vue`采用的。
5 |
6 | > 本篇文章即聚焦Vue监听数据变化的部分。
7 |
8 | # Vue源码解析一:observer
9 |
10 | 要理解Vue,从observer开使是一个不错的选择。因为从本质上来讲,除去生命周期函数,虚拟DOM,组件系统等以外,
11 | Vue首先建立在数据监测之上,可以收集依赖,并在数据变化时自动通知到Vue的实例。
12 |
13 | observer的相关代码在`core/observer`下,数据绑定的逻辑主要集中在`dep/watcher/index/traverse`4个文件中。
14 |
15 | ## observer工作原理简述
16 |
17 | 当有一个表达式,我们可以收集它的依赖,并在数据变化时,让依赖反过来去通知表达式这种变化。用一个例子来示意整个工作流程:
18 |
19 | 1. 首先假设我们有个expOrFn函数,内部返回一个表达式的值:
20 |
21 | ```js
22 | function expOrFn(vm) {
23 | return vm.user
24 | }
25 | ```
26 |
27 | 很显然,expOrFn依赖`vm.user`,当`user`变化时,expOrFn应该自动重新执行。但,怎么知道这种依赖关系呢?
28 |
29 | 2. Vue引入getter来帮助检查依赖,通过运行一次函数来确定依赖。
30 |
31 | 对数据vm来说,假设我们用getter来改写它的所有属性;那么当我们访问`vm.user`的时候,getter函数会执行,
32 | 所以,只要我们执行一次`expOrFn`,它的所有依赖就都知道了!bingo!
33 |
34 | ```js
35 | const vm = { user: 'creeper' }
36 | defineGetter(vm)
37 | let value = expOrFn(vm)
38 | ```
39 |
40 | 一切看起来很简单。然后很自然地,我们加上setter来感知数据的更新。
41 |
42 | 3. Vue用setter来截获数据更新。
43 |
44 | 当vm变化时,我们必须能够感知这种变化,否则收集依赖是完全没有意义的。
45 |
46 | ```js
47 | const vm = { user: 'creeper' }
48 | defineGetterAndSetter(vm)
49 | let value = expOrFn(vm)
50 | // 然后我们更新数据
51 | vm.user = 'who?'
52 | // 因为调用了setter,所以我们可以知道数据更新了!
53 | ```
54 |
55 | 看起来一切都搞定了。但上面的代码只是伪代码,实际开发中,我们必须要解决怎么定义依赖,怎么收集依赖,怎么通知更新的整个流程。
56 |
57 | 4. Vue定义了依赖(`Dep`),并设计了巧妙的 getter/setter 来收集依赖和通知更新:
58 |
59 | ```js
60 | // 依赖,作为纽带来用,本身设计的很薄
61 | class Dep {
62 | // 添加订阅者——即谁依赖这个依赖
63 | addSub(sub) { this.subs.push(sub) }
64 | // 当有变化时,通知订阅者
65 | notify() { this.subs.forEach(sub => sub.update()) }
66 | // 很有意思的方法,下一步重点说,或者直接看源代码的注释
67 | depend() { Dep.target.addDep(this) }
68 | }
69 | ```
70 |
71 | 接下来看看Dep是怎么用在 getter/setter里的:
72 |
73 | ```js
74 | // 假设有数据 vm,我们对 vm 的每个属性调用 defineReactive 来设置 getter/setter
75 | // defineReactive(vm, 'user', 'creeper')
76 | function defineReactive(obj, key, val) {
77 | // 每个属性创建一个dep,这是一个一一对应的关系
78 | const dep = new Dep()
79 |
80 | Object.defineProperty(obj, key, {
81 | enumerable: true,
82 | configurable: true,
83 | get: function reactiveGetter() {
84 | if (Dep.target) {
85 | // 收集依赖
86 | dep.depend()
87 | }
88 | return val
89 | },
90 | set: function reactiveSetter(newVal) {
91 | val = newVal
92 | // 通知数据更新了
93 | dep.notify()
94 | }
95 | })
96 | }
97 | ```
98 |
99 | 如上,getter/setter 配合对应的dep,可以完成依赖收集和更新通知。下面描述整个流程是怎么工作的
100 | (比如`dep.depend()`怎么收集依赖的):
101 |
102 | 5. Vue用`Watcher`来串联整个流程。
103 |
104 | ```js
105 | class Watcher {
106 | // vm 是数据,expOrFn 是表达式,cb 是更新时的回调
107 | constructor(vm, expOrFn, cb) {
108 | this.vm = vm
109 | // 用于收集依赖
110 | this.deps = []
111 | // 收集依赖
112 | this.value = this.get()
113 | }
114 | get() {
115 | // 设置Dep.target,方便依赖收集时 dep.depend 可以正确调用
116 | Dep.target = this
117 | // 调用 expOrFn 来收集依赖
118 | const val = this.expOrFn.call(this.vm, this.vm)
119 | }
120 | // 联系上面的 dep.depend,是不是恍然大悟?
121 | addDep(dep) {
122 | this.deps.push(dep)
123 | dep.addSub(this)
124 | },
125 | // 联系上面的 dep.notify,是不是懂了?
126 | update() {
127 | this.cb.call(this, this.get(), this.value)
128 | }
129 | }
130 |
131 | const vm = { user: 'creeper' }
132 | // 设置 getter/setter
133 | observe(vm)
134 | const exp = vm => vm.user
135 | // 让exp可以监测数据变化
136 | new Watcher(vm, exp, function updateCb() {})
137 | ```
138 |
139 | 以上即整个observer流程,当然,里面简化了很多细节,详细的可看代码注释和下面的核心代码解读。
140 |
141 | ## 核心代码解读
142 |
143 | ### `observe`函数和`Observer`类
144 |
145 | `observe(value, asRootData)`方法用于为value创建getter/setter,从而实现对数据变化的监听;该方法会为value创建对应的Observer实例,而observer则是实际转化value的属性为getter/setter,收集依赖和转发更新的地方。
146 |
147 | observer相关的核心代码是`defineReactive`来创建getter/setter,下面是相关注释:
148 |
149 | ```js
150 | /**
151 | * 把 property 转化为 getter 和 setter
152 | * - 创建dep(dep是一个纽带,连接watcher和数据,getter时收集依赖,setter时通知更新);
153 | * - 在 getter 里面进行依赖收集,在非shallow时递归收集
154 | * - 在 setter 里面进行更新通知,在非shallow时重新创建childOb
155 | */
156 | export function defineReactive(
157 | obj,
158 | key,
159 | val,
160 | customSetter,
161 | shallow
162 | ) {
163 | const dep = new Dep()
164 |
165 | const property = Object.getOwnPropertyDescriptor(obj, key)
166 | if (property && property.configurable === false) {
167 | return
168 | }
169 |
170 | // cater for pre-defined getter/setters
171 | const getter = property && property.get
172 | const setter = property && property.set
173 | if ((!getter || setter) && arguments.length === 2) {
174 | val = obj[key]
175 | }
176 |
177 | // ⚠️
178 | // 大部分情况shallow默认是false的,即默认递归observe。
179 | // - 当val是数组时,childOb被用来向当前watcher收集依赖
180 | // - 当val是普通对象时,set/del函数也会用childOb来通知val的属性添加/删除
181 | let childOb = !shallow && observe(val)
182 | Object.defineProperty(obj, key, {
183 | enumerable: true,
184 | configurable: true,
185 | // watcher 初始化时调用自己的 watcher.get(),最终调用这个 getter,
186 | // 而 dep.depend 执行,把 watcher 放到了自己的 subs 里;所以当
187 | // set 执行时,watcher 被通知更新。
188 | get: function reactiveGetter() {
189 | const value = getter ? getter.call(obj) : val
190 | if (Dep.target) {
191 | dep.depend()
192 | // ⚠️ 现在问题是为什么要依赖 childOb 呢?
193 | // (1)考虑到如果 value 是数组,那么 value 的 push/shift 之类的操作,
194 | // 是触发不了下面的 setter 的,即 dep.depend 在这种情况不会被调用。
195 | // 此时,childOb 即value这个数组对应的 ob,数组的操作会通知到childOb,
196 | // 所以可以替代 dep 来通知 watcher。
197 | // (2)Vue.set/Vue.del API(即下面的set/del函数)中,如果直接把一个对象
198 | // 设置成其它值,或者删除某个对象的key,这个时候也依赖 childOb 来通知。
199 | if (childOb) {
200 | childOb.dep.depend()
201 | // 同时,对数组元素的操作,需要通过 dependArray(value) 来建立依赖。
202 | if (Array.isArray(value)) {
203 | dependArray(value)
204 | }
205 | }
206 | }
207 | return value
208 | },
209 | set: function reactiveSetter(newVal) {
210 | const value = getter ? getter.call(obj) : val
211 | /* eslint-disable no-self-compare */
212 | if (newVal === value || (newVal !== newVal && value !== value)) {
213 | return
214 | }
215 | /* eslint-enable no-self-compare */
216 | if (process.env.NODE_ENV !== 'production' && customSetter) {
217 | customSetter()
218 | }
219 | if (setter) {
220 | setter.call(obj, newVal)
221 | } else {
222 | val = newVal
223 | }
224 | // 对新val重新创建childOb
225 | childOb = !shallow && observe(newVal)
226 | // 通知更新
227 | dep.notify()
228 | }
229 | })
230 | }
231 | ```
232 |
233 | ### 入口`Watcher`
234 |
235 | observer核心入口是`Watcher`,创建一个watcher可以监测数据的变化,并在变化时执行回调。
236 |
237 | 下面是 traverse 的代码:
238 |
239 | ```js
240 | // 递归遍历 val,深度收集依赖
241 | function _traverse(val, seen) {
242 | let i, keys
243 | const isA = Array.isArray(val)
244 | // 如果不是数组或对象,或者是 VNode,或者frozen,则不再处理。
245 | if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
246 | return
247 | }
248 | if (val.__ob__) {
249 | const depId = val.__ob__.dep.id
250 | // 如果已经收集过,则不再重复处理了。
251 | if (seen.has(depId)) {
252 | return
253 | }
254 | seen.add(depId)
255 | }
256 |
257 | // 对数组的每个元素调用 _traverse
258 | if (isA) {
259 | i = val.length
260 | while (i--) _traverse(val[i], seen)
261 | }
262 | // 对子属性(val[keys[i]])访问,即调用 defineReactvie 定义的 getter,收集依赖
263 | else {
264 | keys = Object.keys(val)
265 | i = keys.length
266 | while (i--) _traverse(val[keys[i]], seen)
267 | }
268 | }
269 | ```
270 |
271 | ## 测试和代码执行过程简述
272 |
273 | 下面是一些测试代码,帮助理解observer的运行。
274 |
275 | ```js
276 | const { observe } = require('./dist/core/observer')
277 | const Watcher = require('./dist/core/observer/watcher').default
278 |
279 | function createWather(data, expOrFn, cb, options) {
280 | const vm = {
281 | data: data || {},
282 | _watchers: []
283 | }
284 | observe(vm.data, true)
285 | return new Watcher(vm, expOrFn, cb, options)
286 | }
287 |
288 | const raw = {
289 | s: 'hi',
290 | n: 100,
291 | o: {x: 1, arr: [1, 2]},
292 | arr: [8, 9]
293 | }
294 |
295 | const w = createWather(raw, function expOrFn() {
296 | // 1
297 | return this.data.o
298 | }, (a, b) => {
299 | console.log('--->', a, b)
300 | }, {
301 | deep: false,
302 | sync: true
303 | })
304 |
305 | // 2
306 | raw.o.x = 2
307 | ```
308 |
309 | 我在设置getter的地方加了一些输出语句:
310 |
311 | ```js
312 | get: function reactiveGetter() {
313 | const value = getter ? getter.call(obj) : val;
314 | console.log('call getter --->', key, val, !!_dep.default.target)
315 | if (_dep.default.target) {
316 | dep.depend();
317 |
318 | if (childOb) {
319 | childOb.dep.depend();
320 | console.log('call childOb.dep.depend')
321 | if (Array.isArray(value)) {
322 | dependArray(value);
323 | }
324 | }
325 | }
326 |
327 | return value;
328 | },
329 | ```
330 |
331 | 并且测试代码中,序号1和2下面的一行代码会替换来测试不同的情况,测试结果如下:
332 |
333 | ```
334 | 1. retrun this.data.o 2. raw.o.x = 101
335 | call getter ---> o { x: [Getter/Setter], arr: [Getter/Setter] } true
336 | call childOb.dep.depend
337 | call getter ---> o { x: [Getter/Setter], arr: [Getter/Setter] } false
338 |
339 |
340 | 1. retrun this.data.o 2. raw.o = 101
341 | call getter ---> o { x: [Getter/Setter], arr: [Getter/Setter] } true
342 | call childOb.dep.depend
343 | call getter ---> o 101 true
344 | ---> 101 { x: [Getter/Setter], arr: [Getter/Setter] }
345 |
346 |
347 | 1. retrun this.data.arr 2. raw.arr.push(10)
348 | call getter ---> arr [ 8, 9 ] true
349 | call childOb.dep.depend // 因为child depend,数组的操作可以被监测。
350 | call getter ---> arr [ 8, 9 ] false // push 产生的 getter
351 | call getter ---> arr [ 8, 9, 10 ] true // 调用回调时调用了 this.get()
352 | call childOb.dep.depend
353 | ---> [ 8, 9, 10 ] [ 8, 9, 10 ]
354 | ```
355 |
356 | ## 更多
357 |
358 | 本篇的分析尽量不把Vue其它部分牵扯进来,所以遗留了 `computed` 型watcher和 scheduler 没有涉及。下一篇将解析
359 | instance部分,会把遗留的补上。
360 |
--------------------------------------------------------------------------------