hitao123 / hitao123.github.io Goto Github PK
View Code? Open in Web Editor NEWmy Blog
my Blog
一年之前觉得写一个组件就可以了,能给团队的小伙伴用就很开心了,后来看了 vant 有赞团队的组件库,很喜欢作者那种风格,参照一下,实现一个组件库,下面记录了一下自己实现组件库的过程
npm
, 通过 npm i hui --save-dev, 项目中 import { Button } from 'hui' 引用即可此时项目结构目录
├── build # 构建脚本
├── config # 配置
├── docs # 文档网站
├── packages # 组件文件
├── test # 单元测试
└── utils # 公用方法
Hui
├─ build # 构建脚本
├─ docs # 文档网站
├─ packages # 组件文件
├─ test # 单元测试
└─ types # 类型定义
最开始组件库是没有用法描述的,只有在一个简单 mobile 的列表的 demo 展示,作为一个组件库展示的站点基本结构都是一样的, 导航部分,侧边栏是不同的组件,通过切换可以到不同的组件,中间作为展示部分,右边是模拟器,下半部分可以有导航,上下分页,页脚可以有响应的链接, vant 自己有一个 vant-doc 的组件,只需要相应的配置,就可以灵活的扩展你的站点了,我们的组件描述刚好可以放在 中间的 container 部分
因为每一个 packages 里面的组件都有相应的对该组件的描述,我们可以把这些 md 文件解析成对应的路由组件即可,这样就实现了就可以查看用法了,但是这又有一个问题, 我们平常只处理 js css less vue 这种格式的文件,md 的文件也需要相应的 webpack-loader 去处理, vant 里面是自己实现了一个 fast-vue-md-loader
, 下面是核心代码
const hljs = require('highlight.js');
const MarkdownIt = require('markdown-it');
const wrapper = content => `
<template>
<section v-html="content" v-once />
</template>
<script>
export default {
created() {
this.content = unescape(\`${escape(content)}\`);
}
};
</script>
`;
const highlight = (str, lang) => lang && hljs.getLanguage(lang) ? hljs.highlight(lang, str, true).value : '';
const parser = new MarkdownIt({
html: true,
highlight
});
// webpack 会加载这个loader 当遇到 .md 文件
module.exports = function(source, options) {
this.cacheable && this.cacheable();
options = {
wrapper,
...options
};
return options.wrapper(parser.render(source));
}
看到这里是不是很熟悉,这个把这个文件处理成类似 .vue 单文件,然后剩下的事情交给 vue-loader 做,就像当时引用组件一样,是不是很巧妙
在vant 发现一个 gh-pages 分支,但是代码都是静态文件,之前是了解 github 支持每个分支创建一个 gh-pages 分支通过 https://username.github.io/reponame/ 这种方式能访问你每一个项目 gh-pages 的静态资源, 我们可以把vue-cli build 之后的 dist 目录放到这个分支上就可以访问了, 这样解决了第三个问题,在根据 UA 来判断来使用哪一个入口,就可以实现在 pc 和移动端的访问了
PC 端 查看地址
mint-ui 只能配合 webpack 打包工具使用,如何做到 fis3 这些打包工具也能使用呢? vant 使用的做法是,将package 通过 babel 打包成 commonjs 模块来实现其他工具也可以使用的
最近有接触到和直播相关的业务,就这一块业务深入探讨一下h5直播相关技术,分两个方面探讨,技术原理和实现方案
视频编码:视频流传输中最为重要的编解码标准有国际电联的H.261、H.263、H.264。
音频编码:同视频编码类似,将原始的音频流按照一定的标准进行编码,上传,解码,同时在播放器里播放,当然音频也有许多编码标准,例如PCM编码,WMA编码,AAC编码等等
RTMP: 底层基于TCP,在浏览器端依赖Flash。
HTTP-FLV: 基于HTTP流式IO传输FLV,依赖浏览器支持播放FLV。
WebSocket-FLV: 基于WebSocket传输FLV,依赖浏览器支持播放FLV。WebSocket建立在HTTP之上,建立WebSocket连接前还要先建立HTTP连接。
HLS: Http Live Streaming,苹果提出基于HTTP的流媒体传输协议。HTML5可以直接打开播放。
RTP: 基于UDP,延迟1秒,浏览器不支持。
传输协议 | 播放器 | 延迟 | 内存 | CPU |
---|---|---|---|---|
RTMP | Flash | 1s | 430M | 11% |
HTTP-FLV | Video | 1s | 310M | 4.4% |
HLS | Video | 20s | 205M | 3% |
一个通用的直播模型一般包括三个模块:主播方、服务器端和播放端
直接借助 原生 video 标签,直播流采用 HLS(.m3u8 格式)就可以简单的实现
<video id="video" src="xx.m3u8" type="application/x-mpegURL" x-webkit-airplay="h5" x5-video-player-fullscreen="true" webkit-playsinline="true" x5-playsinline="true" playsinline="true" preload="auto" ><h5>您的系统不支持Video标签</h5>
</video>
完整的技术方案demo 如下(mac 系统)
要想做前端监控,我们需要做到四个部分, 数据收集,数据清洗,数据分析,监控告警,数据部分先不管,先看下前端收集数据部分,是通过 web api Performance 实现的, Performance 的兼容性现在也还是可以的,下面主要看一下实现
我们可以在 onload 的时候发送性能数据到后端,在 js 执行错误的时候
export const monitorInit = () => {
const monitor = {
// 上报地址
url: '',
// 性能数据
performance: {},
// 资源数据
resources: {},
// 错误
errors: [],
// 用户数据
user: {},
// 清空 error 信息
clearError() {
monitor.errors = [];
},
// 上传监控数据
upload() {
// 自定义上传
const data = {
performance,
resources,
errors,
user,
};
fetch(monitor.url, {
method: 'POST',
body: JSON.stringify(data),
headers: new Headers({
'Content-Type': 'application/json',
}),
});
},
// 设置数据上传地址
setURL(url) {
monitor.url = url;
},
};
window.onload = () => {
if (window.requestIdleCallback) {
window.requestIdleCallback(() => {
monitor.performance = getPerformance();
monitor.resources = getResources();
});
} else {
setTimeout(() => {
monitor.performance = getPerformance();
monitor.resources = getResources();
}, 0);
}
};
};
export const getResources = () => {
// eslint-disable-next-line no-undef
if (!window.performance) {
return;
}
// eslint-disable-next-line no-undef
const data = window.performance.getEntriesByType('resource');
const resource = {
xmlhttprequest: [],
link: [],
css: [],
img: [],
script: [],
other: [],
fetch: [],
beacon: [],
time: new Date().getTime(),
};
data.forEach((item) => {
const resourceTypeArray = resource[item.initiatorType];
// eslint-disable-next-line no-unused-expressions
resourceTypeArray && resourceTypeArray.push({
// 资源名称
name: item.name,
// 资源加载耗时
duration: item.duration,
// 资源大小
size: item.transferSize,
});
});
// eslint-disable-next-line consistent-return
return resource;
};
export const getPerformance = () => {
// eslint-disable-next-line no-undef
if (!window.performance) {
return;
}
// eslint-disable-next-line no-undef
const { timing } = window.performance;
const performance = {
// 重定向耗时
redirect: timing.redirectEnd - timing.redirectStart,
// 白屏时间
// whiteScreen: whiteScreen
// dom 渲染耗时
dom: timing.domComplete - timing.domLoading,
// dns 查询时间
dns: timing.domainLookupEnd - timing.domainLookupStart,
// 加载时间
domContentLoaded: timing.domContentLoadedEventEnd - timing.domContentLoadedEventStart,
load: timing.loadEventEnd - timing.loadEventStart,
time: new Date(),
};
// eslint-disable-next-line consistent-return
return performance;
};
// 捕获资源加载错误
window.addEventListener('error', (e) => {
const { target } = e;
if (target !== window) {
monitor.errors.push({
type: target.localName,
url: target.src || target.href,
msg: `${target.src || target.href}is load error`,
time: new Date().getTime(),
});
}
});
// 捕获 js 执行错误
window.onerror = (msg, url, row, col, error) => {
monitor.errors.push({
type: 'javascript',
row,
col,
msg: error && error.stack ? error.stack : msg,
url,
time: new Date().getTime(),
});
};
// 捕获 promise 错误
window.addEventListener('unhandledrejection', (e) => {
monitor.errors.push({
type: 'promise',
msg: (e.reason && e.reason.msg) || e.reason || '',
time: new Date().getTime(),
});
});
大家可能对 vue 使用 defineProperty 方法来实现底层双向绑定也很熟悉了,下面简单实现一下简单的监听一个对象和数组的变化
/**
* 检测对象属性被更改
*/
const ARRAY = 0;
const OBJECT = 1;
/**
* 数组变异方法
* 这几个方法会改变原来数组
*/
const arrayAugmentations = [];
const aryMethods = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
aryMethods.forEach(method => {
let original = Array.prototype[method];
arrayAugmentations[method] = function() {
let result = original.apply(this, arguments);
console.log('🍺 我是数组我被设置了', result)
return result;
};
});
/**
* 对象添加新属性
* @param {*} data
* @param {*} type
*
*/
const objectAugmentations = {};
Object.defineProperty(objectAugmentations, '$add', {
value: function(key, value) {
if (this.hasOwnProperty(key)) return;
Object.defineProperty(this, key, {
configurable: true,
get: function() {
console.log('💖 我被获取了 我的值是', value)
return value
},
set: function(val) {
console.log('🐂 我被设置了 我设置的新值是', val)
if (val === value) return
value = val;
return value
}
});
},
configurable: true,
});
Object.defineProperty(objectAugmentations, '$delete', {
value: function(key) {
if (!this.hasOwnProperty(key)) return;
console.log('⛰ 我被删除了', key)
delete this[key];
},
configurable: true,
writable: true
});
function Observer(data, type) {
if (!(this instanceof Observer)) throw Error('should be called by new')
this.data = data;
console.log(data, '===>');
if (type === ARRAY) {
data.__proto__ = arrayAugmentations; // eslint-disable-line
this.link(data);
} else if (type === OBJECT) {
data.__proto__ = objectAugmentations; // eslint-disable-line
this.walk(data);
}
};
Observer.prototype.walk = function(data) {
if (typeof data !== 'object') return;
for(key in data) {
if (data.hasOwnProperty(key)) {
let value = data[key];
if (typeof value == 'object') {
Observer.create(value)
}
this.convert(key, value);
}
}
}
Observer.prototype.link = function(items) {
items.forEach((value) => {
let ob = Observer.create(value);
if (!ob) return;
});
};
Observer.prototype.convert = function(key, value) {
console.log(key)
Object.defineProperty(this.data, key, {
configurable: true,
enumerable: true,
get: function() {
console.log('💖 我被获取了 我的值是', value)
return value
},
set: function(val) {
console.log('🐂 我被设置了 我设置的新值是', val)
if (val === value) return
value = val;
return value
}
});
}
/**
* 根据不同的数据类型,调用observer构造函数
* @param value {Any} 数据
* @returns {Observer}
*/
Observer.create = function(value) {
if (Array.isArray(value)) {
return new Observer(value, ARRAY);
} else if (typeof value === 'object') {
return new Observer(value, OBJECT);
}
};
let data = {
a: 1,
b: {
c: {
d: 3
}
},
arr: [4, 5]
}
let app = Observer.create(data);
console.log()
console.log('====start==')
console.log(data.a);
data.a = 'aaa';
console.log(data.a)
console.log()
console.log(data.b.c.d);
data.b.c.d = 'dddd';
console.log(data.b.c.d)
console.log()
console.log(data.arr);
console.log()
console.log(data.arr.push(100))
console.log(data.arr)
console.log()
console.log(app.data.$add('age', 25))
console.log(data.age)
console.log(app.data.$delete('age'))
console.log('====end==')
开头简单介绍背景
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX摘要,包含关键下面要介绍的内容
本文包括以下内容:
中间针对一些技术术语可以用 tips 解释一下
中间针对一些技术术语可以用 tips 解释一下
中间针对一些技术术语可以用 tips 解释一下
问题引出
data = {
male: 'boy',
hobby: 'code'
}
`I am a <% male > and i love <% hobby>`
解析模板并输出 I am a boy and i love code
babel 是个转换器,可以将 ES6 转换为 ES5,从而在现有环境执行, 我们在项目中用到这个,但是只知道 babel 做了这一个工作,做一些配置,其他的就交给打包工具了,Babel 默认只转换新的 JavaScript 语法,而不转换新的 API。例如,Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转译。如果想使用这些新的对象和方法,必须使用 babel-polyfill,为当前环境提供一个垫片。
下面我们通过初始化一个项目来看一下 我们项目的依赖在做一些什么工作,先来看一个简单的项目
初始化一个项目,使用 babel-cli 处理
mkdir learn-babel
npm init
touch .babelrc
touch example.js
npm i babel-cli babel-preset-env babel-preset-stage-2 --save-dev
将下面一段配置复制到 babelrc
{
"presets": [
"env",
"stage-2"
],
"plugins": []
}
编辑 example.js
class Vue {
constructor() {
let a;
const b = 10;
}
add(a, b) {
return a + b
}
}
function React() {
const b = 'react'
}
const Augular = 'angular';
修改 package.json script
"scripts": {
"build": "babel example.js --out-file compiled.js"
},
npm run build
查看 compiled.js, 省略部分
'use strict';
var _createClass = function () { ... }();
function _classCallCheck(instance, Constructor) { ... }
var Vue = function () {
function Vue() {
_classCallCheck(this, Vue);
var a = void 0;
var b = 10;
}
_createClass(Vue, [{
key: 'add',
value: function add(a, b) {
return a + b;
}
}]);
return Vue;
}(); // 返回构造函数
function React() {
var b = 'react'; // 直接转换成 var, 如果下面赋值,编译会直接报错
}
var Augular = 'angular'; // 直接转换成 var
最近在写 hui 这个组件库的时候,在对单文件组织进行编译的时候发现需要用到 babel-core, 需要调用Babel的API进行转码,就要使用babel-core模块
还是以上面项目为例
npm i babel-core --save-dev
touch babel.js
const babel = require('babel-core');
const path = require('path');
const options = {
babelrc: true,
extends: path.resolve(__dirname, './.babelrc')
}
const res = babel.transform('class Vue {constructor() {let a;const b = 10;}add(a, b) {return a + b}} function React() {const b = "react"} const Augular = "angular";', options);
console.log(res)
// => { code, map, ast }
// 文件转码(异步)
babel.transformFile('example.js', options, function(err, result) {
result; // => { code, map, ast }
});
// 文件转码(同步)
babel.transformFileSync('example.js', options);
// => { code, map, ast }
// // Babel AST转码
// babel.transformFromAst(ast, code, options);
node babel.js
{
metadata: {
usedHelpers: [ 'createClass', 'classCallCheck' ],
marked: [],
modules: { imports: [], exports: [Object] }
},
options: {
filename: 'unknown',
filenameRelative: 'unknown',
inputSourceMap: undefined,
env: {},
mode: undefined,
retainLines: false,
highlightCode: true,
suppressDeprecationMessages: false,
presets: [],
plugins:
[ [Array],
[Array],
...
],
ignore: [],
only: undefined,
code: true,
metadata: true,
ast: true,
extends: undefined,
comments: true,
shouldPrintComment: undefined,
wrapPluginVisitorMethod: undefined,
compact: 'auto',
minified: false,
sourceMap: undefined,
sourceMaps: undefined,
sourceMapTarget: 'unknown',
sourceFileName: 'unknown',
sourceRoot: undefined,
babelrc: true,
sourceType: 'module',
auxiliaryCommentBefore: undefined,
auxiliaryCommentAfter: undefined,
resolveModuleSource: undefined,
getModuleId: undefined,
moduleRoot: undefined,
moduleIds: false,
moduleId: undefined,
passPerPreset: false,
parserOpts: false,
generatorOpts: false,
basename: 'unknown'
},
ignored: false,
code: '"use strict";\n\nvar _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value"in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }\n\nvar Vue = function () {\n function Vue() {\n _classCallCheck(this, Vue);\n\n var a = void 0;var b = 10;\n }\n\n _createClass(Vue, [{\n key: "add",\n value: function add(a, b) {\n return a + b;\n }\n }]);\n\n return Vue;\n}();\n\nfunction React() {\n var b = "react";\n}var Augular = "angular";',
ast:
Node {
type: 'File',
start: 0,
end: 135,
loc: SourceLocation { start: [Position], end: [Position] },
program:
Node {
type: 'Program',
start: 0,
end: 135,
loc: [SourceLocation],
sourceType: 'module',
body: [Array],
directives: [Array],
_letDone: true },
comments: [],
tokens:
[ [Token],
[Token],
...
]
},
map: null
}
可以看到 code 部分和上面 babel 命令行一样
babel-polyfill 为我们提供了一些什么功能,看名称 polyfill,应该知道是打补丁的,我们可以看我们在什么时候需要引用它,它的工作原理又是什么, 简单来说, babel-runtime 是供编译模块复用工具函数。babel-polyfill是转译没有的api,列入 ES6 里面 的 HashMap
在实现 hui 的过程中,了解到 babel-polyfill
和 babel-transform-runtime
是有区别的babel 官网解释, 如果在工具或者库里面寻找不会改变全局的,选择 transform-runtime plugin,这也意味着你不能使用像 Array.prototype.includes 这样的方法,怎么就改变了全局方法呢?查阅源码去寻找答案
// babel-polyfill lib/index.js 依赖着三个模块,顺着这三个模块找一下
require("core-js/shim");
require("regenerator-runtime/runtime");
require("core-js/fn/regexp/escape");
if (global._babelPolyfill) {
throw new Error("only one instance of babel-polyfill is allowed");
}
global._babelPolyfill = true;
// require("core-js/shim"); 依赖部分 就有es6.object.assign
require('./modules/es6.symbol');
require('./modules/es6.object.create');
require('./modules/es6.object.define-property');
require('./modules/es6.object.define-properties');
require('./modules/es6.object.get-own-property-descriptor');
require('./modules/es6.object.get-prototype-of');
require('./modules/es6.object.keys');
require('./modules/es6.object.get-own-property-names');
require('./modules/es6.object.freeze');
require('./modules/es6.object.seal');
require('./modules/es6.object.prevent-extensions');
require('./modules/es6.object.is-frozen');
require('./modules/es6.object.is-sealed');
require('./modules/es6.object.is-extensible');
require('./modules/es6.object.assign');
require('./modules/es6.promise');
我们来看 ./modules/es6.object.assign 是不是有对 全局有污染
...
参考文档
2018年都跟随公司的技术栈一直在写 Vue, 外边还有好多都是用 React 的,像 阿里系基本就是 React 技术栈的,想去阿里的同学, React 要下功夫,半年前业务用到 React ,当时在 youtubu 视频看到 es6 的装饰模式在 React 的运用,感觉离自己还远,当时就有 HOC(高阶组件)对应有 高阶函数(函数作为入参的函数),看到作者在使用 recompose 这个库,但是来 github 看了一下,发现 star 很多,使用它做HOC, 现在回过来看一下这样做的目的是什么,React 16.8 版本支持 React Hooks ,我看 recompose 作者已经鼓励去使用 React Hooks 了,感觉是 recompose 进入了标准,在 React 官方实现了
redux 使用入门 这是最开始学习 redux 的时候写的如何使用 redux
webpack 运行原理探究
一直在使用 webpack 打包项目,对其中的原理一直不是很了解,想抽一点时间了解整个 webapck 构建过程,loader 机制
插件机制,code splitting
tree shaking
有所了解,我是以 webapck v1.14.0 进行研究的, 代码仓库
demo1 只打包一个入口文件js, 比较简单,可以看看打出来的是什么东西,在浏览器里面是否可以正常运行
(function(modules) {
// 模块缓存
var installedModules = {}; // 私有属性
// require function
function __webpack_require__(moduleId) {
// 判断模块是否在缓存中
if(installedModules[moduleId]) return installedModules[moduleId].exports;
// 创建一个新模块并放到缓存中
var module = installedModules[moduleId] = {
exports: {},
id: moduleId,
loaded: false
};
// 执行模块函数 这里能获取到外边参数, 如果这里没有模块导出,这里函数会被执行以下
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 将模块置位已加载
module.loaded = true;
// 返回module 的 exports 属性, 看到这里才明白所有模块需要 module.exports, 你的模块会被导出
return module.exports;
}
// 暴露 modules 对象 (__webpack_modules__)
__webpack_require__.m = modules;
// 暴露 module cache
__webpack_require__.c = installedModules;
// __webpack_public_path__
__webpack_require__.p = "";
// 加载入口模块 并返回 exports
return __webpack_require__(0);
})
([
/* 0 */
(function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(1);
}),
/* 1 */
(function(module, exports, __webpack_require__) {
var util1 = __webpack_require__(2)
var util2 = __webpack_require__(3)
// var css1 = require('./less/style.less')
// var css2 = require('./less/style2.less')
function OutInfo() {
util1('hahah');
util2('hbo');
}
OutInfo();
}),
/* 2 */
(function(module, exports, __webpack_require__) {
var util2 = __webpack_require__(3);
module.exports = function(name) {
console.log(util2(name) + '----' + '我是 util2方法 在 util1 里面')
}
}),
/* 3 */
(function(module, exports) {
module.exports = function(name) {
console.log(name)
}
})
]);
// 简单化就是一个自执行函数,modules 接受一个函数数组
(function(modules) {
///
})([(function(module, exports, __webpack_require__) { ... }), (function(module, exports, __webpack_require__) { ... }), (function(module, exports) { ... })])
参考资料
const PENDING = Symbol('pending');
const RESOLVE = Symbol('resolve');
const REJECT = Symbol('reject');
class PromiseA {
constructor(executor) {
this.state = PENDING;
this.value = null;
this.reason = null;
this.onFulfilledQueue = [];
this.onRejectedQueue = [];
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (e) {
this.reject(e);
}
}
static resolve(param) {
if (param instanceof PromiseA) {
return param;
}
return new PromiseA((resolve, reject) => {
if (param && (typeof param === 'object' || typeof param === 'function')) {
setTimeout(() => {
param.then(resolve, reject);
});
} else {
resolve(param);
}
});
}
static reject(param) {
return new PromiseA((resolve, reject) => {
reject(param);
});
}
static race(promises) {
return new PromiseA((resolve, reject) => {
if (promises.length > 0) {
for (let i = 0; i < promises.length; i++) {
PromiseA.resolve(promises[i]).then(data => {
resolve(data);
}, (err) => {
reject(err);
})
}
}
});
}
static all(promises) {
return new PromiseA((resolve, reject) => {
let count = 0;
let result = [];
for (let i = 0; i < promises.length; i += 1) {
PromiseA.resolve(promises[i]).then(data => {
result[i] = data;
count += 1;
if (count === promises.length) {
resolve(result);
}
}, (err) => {
reject(err);
})
}
});
}
resolve(value) {
if (this.state === PENDING) {
this.state = RESOLVE;
this.value = value;
this.onFulfilledQueue.forEach(fn => {
fn(value);
});
}
}
reject(reason) {
if (this.state === PENDING) {
this.state = REJECT;
this.reason = reason;
this.onRejectedQueue.forEach(fn => {
fn(reason)
});
}
}
resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
throw TypeError('cycle chain')
}
if (x && (typeof x === 'object' || typeof x === 'function')) {
let flag = false;
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, (res) => {
if (flag) {
return;
}
flag = true;
this.resolvePromise(promise, res, resolve, reject);
}, (err) => {
if (flag) {
return;
}
flag = true;
reject(err);
});
} else {
if (flag) {
return;
}
flag = true;
resolve(x);
}
} catch (e) {
if (flag) {
return;
}
flag = true;
reject(e);
}
} else {
resolve(x);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
let promise = new PromiseA((resolve, reject) => {
if (this.state === RESOLVE) {
setTimeout(() => {
try {
let result = onFulfilled(this.value);
this.resolvePromise(promise, result, resolve, reject);
} catch (e) {
reject(e);
}
})
} else if (this.state === REJECT) {
setTimeout(() => {
try {
let result = onRejected(this.reason);
this.resolvePromise(promise, result, resolve, reject);
} catch (e) {
reject(e);
}
});
} else if (this.state === PENDING) {
this.onFulfilledQueue.push(() => {
setTimeout(() => {
try {
let result = onFulfilled(this.value);
this.resolvePromise(promise, result, resolve, reject);
} catch (e) {
reject(e);
}
})
});
this.onRejectedQueue.push(() => {
setTimeout(() => {
try {
let result = onRejected(this.reason);
this.resolvePromise(promise, result, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
});
return promise;
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(callback) {
return this.then((value) => {
return PromiseA.resolve(callback()).then(() => {
return value;
})
}, (err) => {
return PromiseA.resolve(callback()).then(() => {
throw err;
})
});
}
}
interview 一些基础js问题也是必定会考察的,基础知识很重要,下面有一些常见的基础问题,回顾一下
.center {
position: absolute;
top: 50%;
left: 50%;
transform: (-50%, -50%)
}
.center {
width: 200px;
height: 200px;
margin-left: -100px;
margin-top: -100px;
}
.center {
display: flex;
align-items: center;
justify-content: center;
}
// loading
.loading {
width: 30px;
height: 30px;
border: 2px solid #ccc;
border-radius: 50%;
border-right-color: #f23132;
animation: spin .2s linear infinite
}
@keyframes spin{
0% {
transform: rotate(0deg)
}
100% {
transform: rotate(360deg)
}
}
relative fixed absolute 都会脱离文档流
relative 会占位,relative 相对于它自身进行定位
absolute 是相对于他的父级做参考的,只要父级设置了除 static 的其他属性,如果父级没有设置,就是向上查找,如果都没有设置,那就是 body。
fixed 是相对于浏览器视口的。
px 最基本的单位
rem 是以 html 根元素 font-size 为基准,如 html { font-size: 75px; } 1rem = 75px;
vw : 1vw 等于视口宽度的1%
vh : 1vh 等于视口高度的1%
如 iphone width = 650px 1vw = 6.5px height = 1200px 1vh = 12px
em 是相对于父级元素的 font-size 进行设置, 所以 em 会是动态的
border-box
计算宽度(样式宽度) = content + padding-top + padding-bottom + border-top-width + border-bottom-width
content-box
计算宽度 = content(样式宽度) + padding-top + padding-bottom + border-top-width + border-bottom-width
Number String Null Undefined Boolean Object Symbol
typeof undefined == 'undefined'
typeof null == 'object'
typeof NaN == 'number'
NaN == NaN // false
NaN == null // false
null == undefined // true
如果两个值不是相同类型,它们不相等
如果两个值都是null或者都是undefined,它们相等
如果两个值都是布尔类型true或者都是false,它们相等
如果其中有一个是NaN,它们不相等
如果都是数值型并且数值相等,他们相等, -0等于0
如果他们都是字符串并且在相同位置包含相同的16位值,他它们相等;如果在长度或者内容上不等,它们不相等;两个字符串显示结果相同但是编码不同==和===都认为他们不相等
如果他们指向相同对象、数组、函数,它们相等;如果指向不同对象,他们不相等
如果两个值类型相同,按照===比较方法进行比较
如果类型不同,使用如下规则进行比较
如果其中一个值是null,另一个是undefined,它们相等
如果一个值是数字另一个是字符串,将字符串转换为数字进行比较
如果有布尔类型,将true转换为1,false转换为0,然后用==规则继续比较
如果一个值是对象,另一个是数字或字符串,将对象转换为原始值然后用==规则继续比较
其他所有情况都认为不相等
如果对象有toString()方法,javascript调用它。如果返回一个原始值(primitive value如:string number boolean),将这个值转换为字符串作为结果
如果对象没有toString()方法或者返回值不是原始值,javascript寻找对象的valueOf()方法,如果存在就调用它,返回结果是原始值则转为字符串作为结果
否则,javascript不能从toString()或者valueOf()获得一个原始值,此时throws a TypeError
!![] // true
转换过程分析
String.prototype.toMoney = function() {
//var arr = [];
//var arrStr =[];
//if (Number(this)) {
//arr = String(this).split('.');
//console.log(arr)
//var count = arr[0].length;
//while(count >= 3) {
//arrStr.unshift(arr[0].slice(count - 3, count));
//count = count - 3;
//}
//console.log(arrStr);
//arr[0].length % 3 && arrStr.unshift(arr[0].slice(0, arr[0].length % 3))
//console.log(arrStr);
//}
// console.log(arrStr);
// return arrStr.join(',') + '.' + arr[1] || '';
// return this.split("").reverse().reduce((prev, next, index) => {
// return ((index % 3) ? next : (next + ',')) + prev
// })
return this.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
js 是单线程 一次只能做一件事
在输入的同时可以发请求, IO 所以说非阻塞(同步和 alert 是阻塞的)
task (macroTask) setInterval setImedete setTimeOut XHR, IO
microTask Promise process.nextTick
console.log(1)
setTimeout(() => {
console.log(2)
new Promise(resolve => {
console.log(4)
resolve()
}).then(() => {
console.log(5)
})
})
new Promise(resolve => {
console.log(7)
resolve()
}).then(() => {
console.log(8)
})
setTimeout(() => {
console.log(9)
new Promise(resolve => {
console.log(11)
resolve()
}).then(() => {
console.log(12)
})
})
// 1 7 8 2 4 5 9 11 12
事件冒泡是微软的标准,事件捕获是网景的标准,W3C 统一标准先捕获后冒泡
addEventListener('click', function(){}, false) // default false 冒泡
事件委派
将事件绑定在父级,当有多个子元素需要绑定事件的时候,这样可以提神性能,不需要为每一个绑定事件,而且后面再添加子元素的时候,也是会有事件的
this 只在调用的时候才知道它的上下文,函数定义的时候是不知道的
var foo = 'windowFoo';
function Foo() {
var foo = 'foo';
console.log(this.foo)
}
var a = Foo(); // 'windowFoo';
var b = new Foo(); // undefined
function close() {
var age = 24;
var name = "Bob";
return function() {
console.log(this.age, this.name)
}
}
class Person {
constructor(name, age, male) {
this.name = name;
this.age = age;
this.male = male;
}
}
class Front extends Person {
constructor(name, age, male, hobby) {
super(name, age, male)
this.hobby = hobby;
}
introduce() {
console.log(`my name is ${this.name}, my age is ${this.age} my male is ${this.male}, my hobby is ${this.hobby}`);
return this
}
}
const person = new Front('huahaitao', 24, 'male', 'basketball')
person.introduce()
原型链实现的基本方式
function SuperType() {
this.property = 'SuperType';
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subProperty = 'SubType';
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subProperty;
}
const instance = new SubType();
console.log(instance, instance.getSuperValue(), instance.getSubValue())
function shallowCopy(src) {
var dest = {}
for (var key in src) {
if (src.hasOwnProperty(key)) {
dest[key] = src[key]
}
}
return dest
}
var obj1 = {
'name' : 'zhangsan',
'age' : '18',
'language' : [1,[2,3],[4,5]],
};
var obj2 = extend({}, obj1, true)
function isPlainObj(obj) {
if (!obj || Object.prototype.toString.call(obj) !== "[object Object]") {
return false;
}
var proto = Object.getPrototypeOf(obj);
if (!proto) { return true }
return typeof (Object.hasOwnProperty.call(proto, 'constructor') && proto.constructor) == 'function'
}
function isArray(obj) {
return Object.prototype.toString.call(obj) == '[object Array]'
}
function extend(target, src, deep) {
for (var key in src) {
if (deep && isPlainObj(src[key]) || isArray(src[key])) {
if (isPlainObj(src[key]) && !isPlainObj(target[key])) target[key] = {}
if (isArray(src[key]) && !isArray(target[key])) target[key] = []
extend(target[key], src[key], deep)
} else if (src[key] !== undefined) {
target[key] = src[key]
}
}
return target
}
apply call bind 都是可以改变 this 指向的, apply call 函数是会立即执行 bind 不会立即执行
// 连续的时间内只出现一次
function debounce(fn, delay) {
var timer;
return function() {
var context = this;
var args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
};
}
/**
* 节流 一定时间内一定执行一次
* @param fn
* @param threshhold
* @return { Function }
*/
function throttle(fn, threshhold = 200) {
var last;
var timer;
return function() {
var context = this;
var args = arguments;
var now = +new Date();
if (last && last < last + threshhold) {
console.log(last && last < last + threshhold);
cleartimer(timer);
settimer(function() {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};
}
// var
function hoistVariable() {
if (!foo) {
var foo = 5;
}
console.log(foo); // 5
}
两个函数等价,下面是上面解析之后的样子
// var 的变量提升, var 作用域整个函数
function hoistVariable() {
var foo;
if (!foo) {
foo = 5;
}
console.log(foo); // 5
}
// let 块级作用域
function hoistVariable() {
if (!foo) {
let foo = 5;
}
console.log(foo); // Uncaught ReferenceError: foo is not defined
}
hoistVariable()
// const 不能赋值
const foo = 3.14
foo = 4.14 // Uncaught TypeError: Assignment to constant variable
// 引用地址没有变
const foo = []
foo.push(2)
// 引用地址没有变
const foo = {}
foo.a = { name: 'test' }
// 函数作用域提升
function hoistFunction() {
foo(); // output: I am hoisted
function foo() {
console.log('I am hoisted');
}
}
function hoistFunction() {
function foo() {
console.log('I am hoisted');
}
foo(); // output: I am hoisted
}
hoistFunction();
// 出现同名函数,后面覆盖前面的
function hoistFunction() {
function foo() {
console.log(1);
}
foo(); // output: 2
function foo() {
console.log(2);
}
}
hoistFunction();
// 当变量和函数同名,函数会提到最前面
function hoistFunction() {
foo(); // 2
var foo = function() {
console.log(1);
};
foo(); // 1
function foo() {
console.log(2);
}
foo(); // 1
}
hoistFunction();
101 协议切换
200 正常响应
304 使用客户端缓存
401 未授权
405 GET POST 方法不支持
302 重定向 永久
301 重定向 暂时
500 服务器错误
404 页面不存在 资源不存在
403 forbidden
response header
cache-control: max-age=60;
E-tag: '2222cdcd';
Expires: 2018-12-12
last-modified: 2018-12-12
request header
If-None-Match: 9df406f3bc313e530c6f26d85001eb0b
if-modified-since: 2018-12-12
缓存分为 强制缓存和对比缓存(协商缓存)
强制缓存 不和服务器交互 (浏览器缓存, from disk)
cache-control 设置时间 单位 s
Expires GMT 时间,超过这个时间,过期就会向服务器发送请求
对比缓存 需要和服务器做交互的 返回 304
E-tag 文件hash 是否改变
last-modified 最近一次修改时间
若请求时间大于修改时间, 则返回 304, 若小于 304 需重新请求资源
URL 的最大长度是 2048 个字符 2k
GET 安全性差
POST 无限制
session 因为 session id 的存在,通常要借助 cookie 实现
去除 vue-router 自己实现 路由
function Router() {
this.routes = {};
this.currentUrl = '';
}
Router.prototype.route = function (path, callback) {
this.routes[path] = callback || function () {};
};
Router.prototype.refresh = function () {
console.log('触发一次 hashchange,hash 值为', location.hash);
this.currentUrl = location.hash.slice(1) || '/';
this.routes[this.currentUrl]();
};
window.Router = new Router();
var content = document.querySelector('body');
// change Page anything
function changeBgColor(color) {
content.style.backgroundColor = color;
}
Router.route('/', function () {
changeBgColor('white');
});
Router.route('/blue', function () {
changeBgColor('blue');
});
Router.route('/green', function () {
changeBgColor('green');
});
vue 最后编译的两个 staticRender render function() {} 是干嘛的
全栈
// 数组去重
ES5
function removeDuplicates(arr) {
var map = {}
for (var i = 0; i < arr.length; i++) {
if (arr[i] in map) {
arr.splice(arr[i], 1);
} else {
map[arr[i]] = true;
}
}
return arr
}
removeDuplicates([1, 2, 2, 3, 3, 4, 4, 5])
ES6
Array.from(new Set([...[1, 2, 2, 3, 3, 4, 4, 5]]))
// flat
let data = [1, [2, [[3, 4], 5], 6]];
function flattern(arr, result) {
for (let i = 0; i < arr.length; i++) {
if (typeof arr[i] === 'number') {
result.push(arr[i])
} else {
flattern(arr[i], result)
}
}
}
let result = []
result = flattern(data, result)
// random
function random(start, end, length) {
let arr = [];
start = start ? start : 1;
end = end ? end : 10;
length = length ? length : 10;
let dis = end - start;
for (let i = 0; i < length; i++) {
arr.push(Math.floor(Math.random() * dis) + start)
}
return arr;
}
random()
常见排序算法
快速排序 时间复杂度的计算
rem 适配方案
webpack 按需加载原理
webpack 依赖 lib 问题 配置
let map = new Map();
function getKeyCount(obj) {
let keys = Object.keys(obj);
for (let i = 0; i < keys.length; i += 1) {
if (!map.has(keys[i])) {
map.set(keys[i], 0);
}
if (typeof obj[keys[i]] === 'object') {
if (map.has(keys[i])) {
map.set(keys[i], map.get(keys[i]) + 1);
}
getKeyCount(obj[keys[i]]);
} else {
if (map.has(keys[i])) {
map.set(keys[i], map.get(keys[i]) + 1);
}
}
}
}
let obj = {
a: {
b: 2,
c: 3
},
b: 2,
c: {
a: 1,
b: 2,
c: {
a: 1,
b: 2
}
}
};
getKeyCount(obj)
// a 3 b 4 c 3
console.log(map);
function LinkNode(val, next = null) {
this.val = val;
this.next = next;
}
function LinkList() {
this.head = null;
this.tail = null;
}
LinkList.prototype.add = function add(val) {
const node = new LinkNode(val);
if (this.head === null) {
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
this.tail = node;
}
};
LinkList.prototype.find = function find(val) {
if (this.head === null) {
return null;
}
let current = this.head;
while (current) {
if (current.val === val) {
return current;
}
current = current.next;
}
return null;
};
LinkList.prototype.remove = function remove(val) {
if (this.head === null) {
throw Error('LinkList is empty');
}
if (this.head.val === val) {
// 头结点待删除
this.head = this.head.next;
}
let currentNode = this.head;
if (currentNode !== null) {
while (currentNode.next) {
if (currentNode.next.val === val) {
currentNode.next = currentNode.next.next;
} else {
currentNode = currentNode.next;
}
}
}
};
LinkList.prototype.toArray = function toArray() {
const nodes = [];
let currentNode = this.head;
while (currentNode) {
nodes.push(currentNode);
currentNode = currentNode.next;
}
return nodes;
};
const ll = new LinkList();
ll.add(1);
ll.add(2);
ll.add(3);
ll.add(4);
ll.add(5);
console.log(ll.toArray());
ll.remove(3);
console.log(ll.toArray());
console.log(ll.find(4));
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var minDepth = function(root) {
if (root === null) {
return 0;
}
if (root.left === null && root.right === null) {
return 1;
}
let min = Number.MAX_SAFE_INTEGER;
if (root.left) {
min = Math.min(minDepth(root.left), min);
}
if (root.right) {
min = Math.min(minDepth(root.right), min);
}
return min + 1;
};
function retryAjax(url, num = 1) {
if (num <= 0) {
return Promise.reject(err);
} else {
return fetch(url)
.then(res => {
return res;
})
.catch(err => {
return retryAjax(url, num - 1);
});
}
}
sap
算法题目
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
var lengthOfLongestSubstring = function(s) {
let left = 0
let right = 0
let res = 0
let win = new Map()
while (right < s.length) {
let c = s[right]
right++
if (win.get(c) == undefined) {
win.set(c, 1)
} else {
win.set(c, win.get(c) + 1)
}
while (win.get(c) > 1) {
let d = s[left]
left++
win.set(d, win.get(d) - 1)
}
res = Math.max(res, right - left)
}
return res
}
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
var longestPalindrome = function(s) {
let res = ''
for (let i = 0; i < s.length; i += 1) {
let s1 = helper(s, i, i)
let s2 = helper(s, i, i + 1)
res = s1.length > res.length ? s1 : res
res = s2.length > res.length ? s2 : res
}
return res
function helper(s, left, right) {
while(left >= 0 && right < s.length && s[left] == s[right]) {
left--
right++
}
return s.substring(left + 1, right)
}
}
基础知识
1、什么是 reflow && repaint
https://juejin.cn/post/6844903569087266823
当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为 repaint
浏览器窗口大小发生改变 元素尺寸或位置发生改变 元素内容变化(文字数量或图片大小等等)元素字体大小变化 添加或者删除可见的DOM元素 激活CSS伪类(例如::hover)称之为 reflow
2、defer 和 async 的作用
https://juejin.cn/post/6894629999215640583
async 加载完就执行,顺序无关,一般是广告脚本执行
defer 按照文档的顺序,在 domContentLoaded 之后执行
3、事件循环,输出顺序
https://juejin.cn/post/6844903512845860872
macro-task(宏任务):包括整体代码script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
看一下这段的输出
// 1,7,6,8,2,4,3,5,9,11,10,12
4、防抖和节流
https://juejin.cn/post/6844903669389885453
debounce
search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
throttle
鼠标不断点击触发,mousedown(单位时间内只触发一次)
监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
5、什么是闭包
https://juejin.cn/post/6934500357091360781
闭包是指有权访问另外一个函数作用域中的变量的函数
7、输入 url 整个过程
最开始了解到这个词是在 react, 在开始步入前端这个领域的时候,还是用 angularjs 进行开发,一套 ng 走到底,要写 controller,感觉和写后台 jsp 很像,后来由于项目接触到 react, 慢慢接触 react, vue,也听了同事分享了 virtual DOM, 觉得好高深,为了更好的了解框架是如何运行的,剥离开框架单纯考虑一下 virtual DOM
简单的说 virtual DOM 是一个 js 包含 tagName 属性、children 属性、props 属性的 Object, 因为任何一个 DOM 🌲 上的结点都包括这三个部分,我们使用 js Object 来模拟真实的 DOM 结点
了解了什么是 virtual DOM 之后,我们明白为什么要使用 virtual DOM,大家知道插入真实的 DOM 结点是很耗费资源的, 因为一个真实 DOM上有很多属性以及一些事件,参照大佬一个类比, 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。
vdom
{
children: [],
tagName: 'div',
props: {}
}
realDom
{
accessKey: ""
align: ""
assignedSlot: null
attributeStyleMap: StylePropertyMap {size: 0}
attributes: NamedNodeMap {length: 0}
autocapitalize: ""
baseURI: "https://6f1b67f6e72c43fa8c5528eb6d357abb.production.codepen.plumbing/index.html?d=1548668444648"
childElementCount: 0
childNodes: NodeList []
children: HTMLCollection []
classList: DOMTokenList [value: ""]
className: ""
clientHeight: 0
clientLeft: 0
clientTop: 0
clientWidth: 0
contentEditable: "inherit"
dataset: DOMStringMap {}
dir: ""
draggable: false
firstChild: null
firstElementChild: null
hidden: false
id: ""
innerHTML: ""
innerText: ""
inputMode: ""
isConnected: false
isContentEditable: false
lang: ""
lastChild: null
lastElementChild: null
localName: "div"
namespaceURI: "http://www.w3.org/1999/xhtml"
nextElementSibling: null
nextSibling: null
nodeName: "DIV"
nodeType: 1
nodeValue: null
nonce: ""
offsetHeight: 0
offsetLeft: 0
offsetParent: null
offsetTop: 0
offsetWidth: 0
onabort: null
onauxclick: null
onbeforecopy: null
onbeforecut: null
onbeforepaste: null
onblur: null
oncancel: null
oncanplay: null
oncanplaythrough: null
onchange: null
onclick: null
onclose: null
oncontextmenu: null
oncopy: null
oncuechange: null
oncut: null
ondblclick: null
ondrag: null
ondragend: null
ondragenter: null
ondragleave: null
ondragover: null
ondragstart: null
ondrop: null
ondurationchange: null
onemptied: null
onended: null
onerror: null
onfocus: null
onfullscreenchange: null
onfullscreenerror: null
ongotpointercapture: null
oninput: null
oninvalid: null
onkeydown: null
onkeypress: null
onkeyup: null
onload: null
onloadeddata: null
onloadedmetadata: null
onloadstart: null
onlostpointercapture: null
onmousedown: null
onmouseenter: null
onmouseleave: null
onmousemove: null
onmouseout: null
onmouseover: null
onmouseup: null
onmousewheel: null
onpaste: null
onpause: null
onplay: null
onplaying: null
onpointercancel: null
onpointerdown: null
onpointerenter: null
onpointerleave: null
onpointermove: null
onpointerout: null
onpointerover: null
onpointerup: null
onprogress: null
onratechange: null
onreset: null
onresize: null
onscroll: null
onsearch: null
onseeked: null
onseeking: null
onselect: null
onselectionchange: null
onselectstart: null
onstalled: null
onsubmit: null
onsuspend: null
ontimeupdate: null
ontoggle: null
onvolumechange: null
onwaiting: null
onwebkitfullscreenchange: null
onwebkitfullscreenerror: null
onwheel: null
outerHTML: "<div></div>"
outerText: ""
ownerDocument: document
parentElement: null
parentNode: null
prefix: null
previousElementSibling: null
previousSibling: null
scrollHeight: 0
scrollLeft: 0
scrollTop: 0
scrollWidth: 0
shadowRoot: null
slot: ""
spellcheck: true
style: CSSStyleDeclaration {alignContent: "", alignItems: "", alignSelf: "", alignmentBaseline: "", all: "", …}
tabIndex: -1
tagName: "DIV"
textContent: ""
title: ""
translate: true
}
上面说到的比较两个 virtual DOM 的改变的关键就是 diff 算法
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.