领导自己
- 保持好奇心;
- 不分你我,不分边界;
- 坚持不懈;
- Done is done。
何为靠谱
- 凡事有交代;
- 件件有着落;
- 事事有回应。
Personal blog
Home Page: https://github.com/yaofly2012/note/issues
领导自己
何为靠谱
Treat Document Level Touch Event Listeners as Passive
AddEventListenerOptions defaults passive to false. With this change touchstart and touchmove listeners added to the document will default to passive:true (so that calls to preventDefault will be ignored)..
If the value is explicitly provided in the AddEventListenerOptions it will continue having the value specified by the page.
This is behind a flag starting in Chrome 54, and enabled by default in Chrome 56. See https://developers.google.com/web/updates/2017/01/scrolling-intervention
addEventListener第三个参数option
有个配置:
passive: Boolean,设置为true时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
即passive为true时就是绑定个passive事件处理函数,并且该事件处理函数中不能调用preventDefault
。
Improving Scroll Performance with Passive Event Listeners
浏览器对一些事件都存在默认的行为,比如点击a标签,浏览器会打开a标签指定的链接,滚动鼠标会滚动页面。通过调用事件对象的preventDefault方法可以阻止浏览的默认行为。
但是对于页面滚动的事件会有些问题:
当你触摸滑动页面时,页面应该跟随手指一起滚动。假如此时也绑定了一个 touchstart 事件,这时浏览器就犯迷糊了:如果你在事件绑定函数中调用了 preventDefault,那么页面就不应该滚动,如果你没有调用 preventDefault,页面就需要滚动。但是你到底调用了还是没有调用,浏览器不知道。只能先执行你的事件处理函数执行完后,浏览器才知道,“哦,原来你没有阻止默认行为,好的,我马上滚”。此时,页面开始滚。
preventDefault
在绑定事件的时候通过第三个参数配置向浏览器承诺回调函数不会调用preventDefault
,这样浏览器就不等待回调函数执行后才发起重绘请求(注意:浏览器渲染还是等回调执行完之后,单线程的)。
但是如果你撒谎(还在调用了preventDefault)则浏览器会给报提示信息的:
Chrome Treat Document Level Touch Event Listeners as Passive
With this change touchstart and touchmove listeners added to the document will default to passive:true (so that calls to preventDefault will be ignored)..
虽然这样写,但是测试下来不只是document,其他元素的touchstart, touchmove
事件(不包含touchend
)都是默认为true。
Making touch scrolling fast by default作出了解释:
In the future we hope to make passive true the default for all touchstart and touchmove listeners,
Making touch scrolling fast by default作出了解释:
We looked at the percentage of cancelable touch events that were sent to a root target (window, document, or body) and determined that about 80% of these listeners are conceptually passive but were not registered as such. Given the scale of this problem we noticed a great opportunity to improve scrolling without any developer action by making these events automatically "passive".
WICG/EventListenerOptions 标准化了:
Passive event listeners are a new feature in the DOM spec that enable developers to opt-in to better scroll performance by eliminating the need for scrolling to block on touch and wheel event listeners
页面渲染和JS执行是同一个线程,页面滚动会造成重新页面的渲染。如果事件回调函数比较耗时同样会造成页面滚动卡顿,并且这是passive事件不能解决的。常见的解决方案:
UE全局变量是UEditor的最顶层命名空间
// 全局变量1: 默认配置对象,可以在其他文件里定义
UEDITOR_CONFIG = window.UEDITOR_CONFIG || {};
// 全局变量2:定义全局变量baidu
var baidu = window.baidu || {};
window.baidu = baidu;
// 全局变量3:UE
window.UE = baidu.editor = {
plugins: {},
commands: {},
instants: {},
I18N: {},
_customizeUI: {},
version: "1.5.0"
};
var dom = (UE.dom = {});
调用方法UE.registerUI
添加新的UI组件。
UEditor所有对富文本编辑效果的操作都是通过命名完成的,已经内置了很多命令,源码的plugins目录都是命名定义文件。
命令
是个对象,有个名称和对应的动能函数。
1 => 代表当前命令在当前选区内已执行
0 => 代表当前命令在当前选区内未执行, 但处于可用状态
-1 => 代表当前命令在当前选区内处于不可用状态
queryCommandState
方法用来查询命令的状态。一般toolBars上的UI组件都会监听selectionchange
事件,然后查询其对应的命名的状态控制自己的状态(disabled, checked)。
editor.registerCommand
UE.commands
对象里添加新命名对象statleWihieRevalidate PK netWorkFirst
serving-suggestions
window和sw scope共享CacheStorage ? !
处理函数的content.params的值来自match函数的返回值,但是如果返回值是bool值则content.params为undefined(要看源码才知道为啥这样)。
用来管理注册的route。处理流程见图
workbox.routing是DefaultRoute对象
registerNavigationRoute(cachedAssetUrl, options)
注册个NavigationRoute,匹配的请求都返回由cachedAssetUrl指定的预缓存Response。如果预缓存里没有,则从网络获取(此时Response不会被缓存)。
registerRoute(capture, handle)
capture可以是
const matchCallback = ({ url }) => {
return url.href === captureUrl.href;
};
RegExp -> 创建RegExpRoute
Function -> 创建Route
Route -> 直接使用参数Route,忽略handle参数了
const matchCb = ({url, event}) => {
return (url.pathname === '/special/url');
};
For requests from the same origin, this regular expression will match as long as the request’s URL matches the regular expression.
However, for cross-origin requests, regular expressions must match the beginning of the URL
主要目的是为了开发自己明白自己是处理跨域的资源还是要处理同域的资源(同域认为是自己的)。
RegExpRoute的源码:
const match = ({ url }) => {
const result = regExp.exec(url.href);
// Return null immediately if there's no match.
if (!result) {
return null;
}
if (url.origin !== location.origin && result.index !== 0) {
return null;
}
return result.slice(1);
};
通过判断 result.index是否为0来确定的(故意的)。这样我们可以通过正则的写法来绕过跨域的限制。
所以workbox的“免责”来了:
If you wanted to match both local and third parties you can use a wildcard at the start of your regular expression, but this should be done with caution to ensure it doesn’t cause unexpected behaviors in you web app
It will only match incoming Requests whose mode is set to navigate
扫盲什么是navigation request
NavigationRoute只匹配navigation Request,默认情况下其他的都通过,可以使用blacklist正则表达式数组配置黑名单增加更严格的限制。
如果要修改默认行为可以使用whitelist配置,whitelist的默认值是[/./],即匹配任意URL。
_math源码如下:
_match({ event, url }) {
if (event.request.mode !== 'navigate') {
return false;
}
const pathnameAndSearch = url.pathname + url.search;
if (this._blacklist.some(regExp => regExp.test(pathnameAndSearch))) {
return false;
}
if (this._whitelist.some(regExp => regExp.test(pathnameAndSearch))) {
return true;
}
return false;
}
注意:
workbox.precaching.precacheAndRoute([
'/styles/example.ac29.css',
{
url: '/index.html',
revision: 'as46',
}
]);
url
表示资源的路径
revision
是可选的,如果省略则取值为url
的值,即认为资源的url
也可代表资源的版本。
版本可以是任意字符串,一般以资源内容的hash值作为版本号。
只针对运行时缓存
workbox只对“有效的”响应进行缓存,那判断响应的有效性没有统一的标准,开发者可以使用cacheableResponse模块进行自定义“什么是有效的响应”。
默认行为
查看浏览器的serviceWorker
chrome://inspect/#service-workers
写的不错,必须看看
https://www.slideshare.net/lijing00333/javascript-engine
词法作用域(声明提前,作用域链)
值的集合,并且值不能重复。数组也是值的集合,但是数组的元素可以是重复的。
Set
和数组Set
new Set(array)
Set
转成数组Array.from
var a = [1, 2, 3, 2];
a = Array.from(new Set(a)); // 或者a = [...new Set(a)]
注意:Array.indexOf
和Set
使用的是不同的相等判断逻辑
Set
是如何实现的同Map
的实现(下面会说),只不过Set
只有key,没有value。
nodejs eventloop PK 浏览器 eventloop
/*
{
parse:{Function},
parseScript:{Function},
parseModule:{Function},
version: {string},
Syntax: {object} // 定义的类型常量
}
*/
var esprima = require('esprima')
express = 路由匹配(routing) + 中间件(middleware) ?
核心概念:
- Babel is a JavaScript compiler.
- Use next generation JavaScript, today。Beyond!
是"源码到源码"的编译器。
从v7开始,babel的所有模块都单独的定义在@babel
下面,可以单独使用各个模块。
模块也采用新的命名规则,详细见Upgrade to Babel 7
@babel/核心模块
@babel/preset-{预设名字}
@babel/{预设名字}
@babel/plugin-{插件名字}
@babel/{插件名字}
@babel/polyfill
babel核心模块,转换工作的依赖的模块。所有babel的工作都离不开这个模块。
大胆猜测大概的功能:
@babel/core
模块只是负责转换代码,作为dev deps
。
通过命名行使用babel编译文件,内部依赖@babel/core
。
具体的转码工作由plugins完成的。
{
"plugins": ["@babel/plugin-transform-arrow-functions"]
}
presets
执行;预定义的一组plugin集合,方便书写配置。
plugins
执行;presets
是具有语义环境的(es2015,es2016分表示ES的不同版本),一般是由依赖关系的。代替之前各种ES版本的presets(es-2015,es-2016)。现在统一使用env表示各种版本ES(简化了ES的配置)。
https://babeljs.io/docs/en/configuration
https://babeljs.io/docs/en/config-files#configuration-file-types
默认配置和显示的配置
默认配置
没有配置文件,命令行不指定配置
编程的方式
命令行参数方式
.babelrc,静态的JSON文件
在项目根目录(即package.json所在的目录)有这个文件就读取这个文件的配置,就不存在默认的配置了,必须显示的配置。
.babelrc.js
package.json
babel.config.js
js
文件的 ?Since Babel assumes that your code will run in an ES5 environment it uses ES5 functions
如何修改到ES3环境?
Babel DOC 使用章节。Babel是个需要认知研究的工具,
__dirname, process.cwd的区别。
__dirname不是全局变量,它是模块的全局变量。见官方文档模块包装器
(function(exports, require, module, __filename, __dirname) {
// 模块的代码实际上在这里
});
将路径(path)字符串模式,转成正则表达式。可以用于对request进行匹配,并解析路径参数。
虽然path-to-regexp目前已经是2.4.0版本了,但是ExpressJs 4.X依赖的是0.17版本的,有些特性在ExpressJs还不能使用。
var pathToRegexp = require('path-to-regexp')
var re = pathToRegexp(path, keys?, options?)
Please note: The RegExp returned by path-to-regexp is intended for ordered data (e.g. pathnames, hostnames). It does not handle arbitrary data (e.g. query strings, URL fragments, JSON, etc).
path-to-regexp
只处理URL中有序的数据部分(参数匹配依赖顺序),即URL的host, path部分,不处理QueryString, URL片段(这部分数据是无序的,如?a=1&b=2
和?b=2&a=1
是一样的)。
path-to-regexp
返回的正则分组一一对应的,这样可以找到path参数名字和值。格式:
:参数名称
([^\/]+?)
,并且是懒惰模式的。只有捕获分组正则表达式字符串
?
+
, *
([^\/]+?)
,可以修改这个正则::参数名称(自定义正则字符串)
/index
和/index/
是不同的path,而非严格模式则视为相同的path。$
,默认true,即匹配结束符。end
为false,并不是表示匹配path字符串任意位置,此时它是以'/'和'$'作为可选的结束位置。可以看看生成的结果:var regExp1 = pathToRegexp('/foo', []) // => /^\/foo\/?$/
var regExp2 = pathToRegexp('/foo', [], {end: false}) // => /^\/foo\/?(?=\/|$)/
regExp2会匹配'/foo', '/foo/bar',但不会匹配'/fooBar'.
5. start
<bool>, 是否添加起始符^
,默认true,即匹配起始符。0.1.7不支持,即一直为true。
使用在线的Tool Express Route Tester
a = [1, 2]
b = [1, 2]
print(a == b) #True
这个跟JS里不是一样的,JS中比较的是变量本身的值。估计是因为python只有引用类型的变量,所以比较运算符才这样搞。
a = 1
b = a
print(a is b)
print(b is a)
说明Python只有引用类型,没有值类型。
print("A") if a > b else print("=") if a == b else print("B")
一切都是对象?
http://python.jobbole.com/81646/
函数定义的时候会把函数涉及的闭包变量,局部变量进行收集:
Python的变量定义发生在赋值操作的时候。
代码有问题的原因?
def counter():
count = 0
def c():
count += 1
return count
return c
c = counter()
c()
A file containing a set of functions you want to include in your application.
定义在模块里全局变量和方法
import moduleName as alisName
form moduleName import 模块输出列表
模块和作用域???
导入包查找方式???
self.addEventListener('install', function(event) {
event._from = 'callback1'
console.log('install callback 1: ' + (Date.now()/1000))
var i = 0;
while(i < 1000000) {
++i;
}
// 多次调用
event.waitUntil(new Promise(function(resolve) {
setTimeout(() => {
console.log('resolve 2s')
resolve()
}, 2000)
}))
event.waitUntil(new Promise(function(resolve, reject) {
setTimeout(() => {
console.log('resolve 3s')
reject() // Reject promise
}, 3000)
}))
})
// 多次绑定Install事件
self.addEventListener('install', function(event) {
event._from = 'callback2'
console.log('install callback 2: ' + (Date.now()/1000))
event.waitUntil(new Promise(function(resolve) {
setTimeout(() => {
console.log('resolve 5s')
resolve()
}, 5000)
}))
})
可以绑定多个fetch事件。
self.addEventListener('fetch', function(event) {
debugger
event._from = 'callback1'
console.log('fetch callback 1')
//event.respondWith(fetch(event.request))
})
self.addEventListener('fetch', function(event) {
console.log(`fetch callback 2, from:${event._from}`)
event._from = 'callback2'
//throw 'abc'
event.respondWith(fetch(event.request))
})
self.addEventListener('fetch', function(event) {
console.log(`fetch callback 3, from: ${event._from}`)
})
ES5没块作用域的,见B_JS权威指南,这个问题在ES6中得以解决。ES6中let关键字声明的变量具有块作用域。
js中一对花括号就构成了一个块。常见的块有:
'use strict';
function show(){
{
var a = 1;
let b = 1;
}
console.log(a);
console.log(b); // 在块外部访问变量b,导致抛ReferenceError.
}
show();
先看下经典的循环闭包例子(显示结果也不出意外):
'use strict';
function show(){
var arr = [];
for(var i =0;i !== 4; ++i) { // var变量
arr.push(function(){
console.log('callback_' + i);
})
}
return arr;
}
show().forEach(function(callback, index){
callback();
});
如果把i变量该let变量问题就简单的解决了:
function show_Let(){
var arr = [];
for(let i =0;i !== 4; ++i) { // let变量,每次循环都创建个新的块作用域对象
arr.push(function(){
console.log('callback_' + i);
})
}
return arr;
}
show_Let().forEach(function(callback, index){
callback();
});
因为i是块作用域,并且每次循环都会创建新的块作用域对象,所以每次闭包的i变量都处于不同的块作用域对象中。上面的问题也可以这样解决:
'use strict';
function show(){
var arr = [];
for(var i =0;i !== 4; ++i) {
let j = i; // 创建个let变量j
arr.push(function(){
console.log('callback_' + j); // 访问变量j
})
}
return arr;
}
从侧面也说明了let变量不会发生声明提升(var变量的特性)。
let a;
console.log(a); // undefined
console.log(b); // ReferenceError
let b;
let a; // SyntaxError
let a;
let b; // SyntaxError
var b;
浏览器中不在函数中声明的var变量是全局对象window的属性,但是对于全局的let变量是作为全局对象window的属性的。
ES6中除了引入let变量,还引入了const变量。const变量是一种特殊的let变量。除了3.2列出的特殊性外,其他的同let的用法。
const a = undefined; // 必须初始化,即使用undefined初始化
const b; // SyntaxError
const b = (function() { return 'hello'; })(); // 函数调用表达式
const变量是指不可用修改const变量本身的值:对于值类型的const变量表示const变量的值可以修改,对于引用类型的const变量表示不能修改变量的引用对象,但是可以修改被引用对象的属性。
const a = 12; //定义必须初始化
a = 1; // TypeError
const b = {
name: 'john'
}
b.name = 'hi'; //OK
b = {}; // TypeError
在参考https://nodejs.org/en/docs/es6/ 中,默认情况下Node.js是支持const块作用域的。但是测试发现非严格模式下const变量的行为并不具备块作用域,而是像不可变的var变量。
console.log(a)
{
const a = 12;
}
console.log(a)
文档中有这么一段话:
对同一文件多次使用 fs.writeFile 且不等待回调,是不安全的。 对于这种情况,建议使用 fs.createWriteStream。
因为方法是异步的,多次调用无法保证执行的顺序。使用fs.createWriteStream可以显示的控制写入顺序。
var fs = require('fs');
fs.writeFile('demo.txt', 'Node.js 中文网', (err) => {
if (err) throw err;
console.log('文件已保存!');
});
fs.writeFile('demo.txt', 'Node.js 中文网 2', (err) => {
if (err) throw err;
console.log('文件已保存 2!');
});
fs.writeFile('demo.txt', 'Node.js 中文网 3', (err) => {
if (err) throw err;
console.log('文件已保存 3!');
});
顺序无法控制
var ws1 = fs.createWriteStream('demo.txt');
ws1.end('文件已保存!', 'utf-8', (err) => {
if (err) throw err;
console.log('文件已保存!');
});
var ws2 = fs.createWriteStream('demo.txt');
ws2.end('文件已保存!2', 'utf-8', (err) => {
if (err) throw err;
console.log('文件已保存!2');
});
var ws3 = fs.createWriteStream('demo.txt');
ws3.end('文件已保存!3', 'utf-8', (err) => {
if (err) throw err;
console.log('文件已保存!3');
});
顺序可控。
选择器是匹配DOM元素的模式。
选择器是应用整个DOM树的,不论在HTML文档任意位置定义的CSS都会在整个DOM树中查找匹配。
他们都是选择器
/^^hello\b(?!\w)/.test("hello "); // true
位置匹配^
重复匹配一个位置,相当于一个位置匹配;
位置匹配\b
和(?!\w)
都是对同一个位置进行匹配,字符b
后面的位置即是个单词结尾又是非单词字符(本例是空格字符)的前面
// 规则1 = 长度6到15位,必须是数字,字母,下划线,感叹号,中划线
/^[0-9a-zA-Z_!-]{6,15}$/.test('abc123'); // true,很容易实现
// 规则2 = 规则1 + 必须包含大写字母
/(?=.*[A-Z])^[0-9a-zA-Z_!-]{6,15}$/.test('abc123'); // false
/(?=.*[A-Z])^[0-9a-zA-Z_!-]{6,15}$/.test('abC123'); // true
// 规则3 = 规则2 + 必须包含下划线
/(?=.*_)(?=.*[A-Z])^[0-9a-zA-Z_!-]{6,15}$/.test('abC123'); // false
/(?=.*_)(?=.*[A-Z])^[0-9a-zA-Z_!-]{6,15}$/.test('abC123_'); // true
原理就是通过找满足条件的位置来判断目标字符串是否符合指定的规则。规则2(规则3同理)的正则表达式相当于对起始位置添加了多个匹配条件。
6. 回溯
为了判断是否匹配完成,进行了一些尝试。尝试的副作用就是造成回溯(造成匹配位置倒退的行为)。
回溯会影响性能,写正则尽量规避造成回溯。
model:管理数据状态
view:页面结构
viewModel:描述状态和界面结构的关系的一种模板语法
rpx可靠吗?
所以运算结果会和预期结果有一点点偏差
app.json,app.wxss分别是小程序的公共配置,公共样式。但app.js不是小程序的公共逻辑,它是小程序的逻辑,一个小程序只有一个【实例对象】,它使用创建小程序实例的逻辑。
navigationBarTextStyle
还会影响状态栏的文本颜色。页面的.json只能设置 window 相关的配置项,以决定本页面的窗口表现,所以无需写 window 这个键。
//app.js
App({
onLaunch: function (param) {
this.param = param;
this.param.from = 'onLaunch'
},
onShow: function(param) {
console.log('onShow')
console.log(this.param === param); // true
console.log(param.from); // onLaunch
}
})
这样保证了小程序切到前台时保留最原始(onLauch)的入参。
2. 统一的异常和404处理回调
当通过APP函数实例话小程序对象后,开始创建页面对象了。
页面对象包含生命周期函数,窗口的事件监听函数,以及和视图的通信?。
? onLoad,onShow中指向setData的影响?
一个页面对象只会创建一次,也只会有一个第一次渲染,所以onLoad, onUnload, onReady在页面对象整个生命周期中只会触发一次。
?APP的生命周期和Page的生命周期的关系
几种页面跳转方式 & 对页面栈对影响
重定向:就是把当前page对象替换掉,所以当前page对象也会被销毁掉。只有路由有记录的page对象才会被缓存?
tabBar页面和非tabBar页面路由方式的不同?
页面跳转会导致tabBar消失?难道tabBar只在首页展示或者tabBar页面才会显示tabBar?
window.requestIdleCallback()
注册个函数(插入一个队列里),等浏览器空闲时(browser's idle periods)才执行逐个这些函数。
requestIdleCallback will schedule work when there is free time at the end of a frame, or when the user is inactive.
A reference to a function that should be called in the near future, when the event loop is idle.
a frame
是什么?callback
执行顺序 。注意:callback
的具体什么时候调用是未知的,不要做一些依赖执行顺序的操作。
timeRemaining
获取从调用requestIdleCallback函数注册回调函数,到函数被执行时距离超时还有多久(单位ms)
因为剩余时间需要实时计算,所以是通过方法获取的,这样每次调用都要重新计算;
callback
最好在剩余时间内执行,如果执行不完最好终止执行,并重新调用requestIdleCallback
注册新的回调。
时间分片
当EventLoop空闲时,timeRemaining
会比较大,一般最大是50ms。
以防止突出插入新的任务(比如用于突然发生交互,获取请求返回等)。
didTimeout
表示是否因为超时而造成的回调执行
Promise的resolve(reject)操作也不建议放在里面,因为Promise的回调会在idle的回调执行完成后立刻执行,会拉长当前帧的耗时,所以不推荐
不懂?看看这个从 event loop 规范探究 javaScript 异步及浏览器更新渲染时机
用setTimeout
模拟。
window.requestIdleCallback = window.requestIdleCallback || function(handler) {
let startTime = Date.now();
return setTimeout(function() {
handler({
didTimeout: false,
timeRemaining: function() {
return Math.max(0, 50.0 - (Date.now() - startTime));
}
});
}, 1);
}
didTimeout
是无法模拟的,直接赋值false
;timeRemaining
函数记得设置个最大值50ms。grunt
gulp
webpack
vuejs为什么放弃webpack改成gulp打包工具了
任何可以使用字符串的地方都可以使用模块字符串代替。
Object.prototype.toString.call(`a`); // "[object String]"
console.log(`hello`[1]); // "e"
console.log(`
hello, world!
I'm ok.
`)
多行时``之间的任何字符串都是字符串内容,包含不可见字符(换行,回车,tab等)。
var msg = 'world';
console.log(`hello ${msg}`)
大括号内部可以放入任意的JavaScript表达式
模板字符串也可以视为是JS表达式,该表达式返回的是字符串。并且可以通过自定义个函数(标签函数)来修改模板字符串默认的构建字符串的行为。
alert`123`
// 等同于
alert(123)
function tag(s, v1, v2) {
console.log(s[0]);
console.log(s[1]);
console.log(s[2]);
console.log(v1);
console.log(v2);
return "OK";
}
var a = 5;
var b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
// 参数1点取值等同于
var s = "Hello ${ a + b } world ${ a * b }".split(/\${.*?}/);
组织:
表单,表单域
Layer(path, options, fn)
怎么理解这个对象layer
呢?本身的功能是路由匹配和调用中间件函数。但是Router和Route内部是使用了Layer对象。
匹配path并解析路径参数
统一调用中间件函数的方式
统一调用错误处理中间件函数方式
<anonymous>
.this.name = fn.name || '<anonymous>';
// set fast path flags
this.regexp.fast_star = path === '*'
this.regexp.fast_slash = path === '/' && opts.end === false
*
,表示匹配所有的pathJS是基于原型的面向对象模式,而不是基于类的面向对象。ES2015提供了一个语法糖,可以像写基于类的面向对象模式的代码,但本质还是基于原型的面向对象。
注意:
不能以类语言中的类来思考ES2015的类,而是以JS构造函数的方式来思考ES2015的类。
构造方法constructor
是可选的,并且一个类最多只能有一个构造方法。如果含有多个构造方法,则报语法错误;
命名的类表达式的名字只在类内内可见(跟函数表达式类似);
class
也是可以声明块作用域变量,特性跟let
类似;
可以把class
视为只能声明类变量的特殊的let
;
必须使用new
方式调用class
(构造函数没法做的事,class做到了);
class
的所有方法(包括静态方法和实例方法)都没有prototype
属性,也没有实现[[construct]]
内部方法,不能使用new
来调用。
类成员方法已经有明确的this
变量取值对象了(即本类的实例),防止滥用。
类实例对象的原型的属性都是不可枚举的
JS所有内置对象的prototype
属性都是不可枚举的,class
声明的“构造函数”的prototype
属性同样也是不可枚举的,所以类实例对象的原型的属性都是不可枚举的
。
ES2015支持实例创建,构造方法,静态方法,继承,父类调用。
在类语言中可以在类体中直接定义成员的方法,但是ES2015中是不可用的。成员变量只能在构造方法和非成员方法中声明。为了统一类实例的成员变量,一般在构造方法中声明所有的成员变量。
用static关键字修饰的成员方法表示静态成员方法。静态成员方法只能通过类名引用。"prototype"不能作为静态方法的名字(该名字是用于原型继承用的)。
var a = new A();
a.show();
// a.count(); 报类型错误
A.count();
ES2015中并没有定义如何定义静态变量,但是可以通过以下几种方式实现。
类只是构造函数的语法糖,所有类也是个构造函数(也是JS对象),可以直接在类上添加成员,这样成员就是静态。
class A{
constructor(name){
this.name = name;
this.age = 20;
}
show(){
console.log('show of instance of A');
}
static count(){
console.log('count A: ' + this.age);
}
}
A.Age = 1; // 定义静态成员变量
A.Say = function(){ // 定义静态成员方法
console.log('My age is ' + A.Age);
}
A.Say(); // 调用静态方法
class A{
constructor(name, age){
this.name = name;
this.age = age;
}
show(){
console.log('A instance: show: name=' + this.name + ', age=' + this.age);
}
static count(){
console.log('A: count');
}
}
// 继承A
class B extends A {
show() { // 覆盖A成员方法
console.log('B instance: show: name=' + this.name + ', age=' + this.age);
}
say(){ // 新加的成员方法
console.log('B instance say: ');
}
static count(){ // 覆盖A静态成员方法
console.log('B: count: ');
}
}
//B.count();
var b = new B('qyao_B', 24); // 继承A的构造方法
var a = new A('qyao_A', 23);
b.show();
b.say();
B.count();
a.show();
// a.say(); // 未定义
A.count();
super
访问父类成员constructor
里直接调用super(arg...)
;super
只能访问父类的成员方法(静态方法),super
访问父类静态方法,反之静态方法不可以通过super
访问父类的成员方法;super
更像是个占位符,子类成员方法访问的任意父类成员方法(没有同名限制)。super
的使用方式完全取决于子类在什么地方调用super
class A{
constructor(name, age){
this.name = name;
this.age = age;
}
show(){
console.log('A instance: show: name=' + this.name + ', age=' + this.age);
}
static count(){
console.log('A: count: ');
}
}
class B extends A {
constructor(name){
super(name , 26); // 调用父类构造方法
}
show() {
super.show(); // 调用父类继承show成员方法
console.log('B instance: show: name=' + this.name + ', age=' + this.age);
}
say(){
super.show(); // 也可以调用父类继承show成员方法
console.log('B instance say: ');
}
static count(){
super.count(); // 调用继承父类的count静态成员方法
console.log('B: count: ');
}
}
//B.count();
var b = new B('qyao_B', 24);
b.show();
b.say();
B.count();
constructor
方法),并且super
语句必须在this
引用之前。this
变量。ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
从报错信息中还可以看到在访问this变量之前要先调用父类构造函数。
class A{
show(){
}
}
class B extends A {
constructor(){
super(); // 父类没有定义构造方法,也必须调用,否则在创建D实例时就抛异常
}
}
class C extends A {
constructor(){
super();
this.name = 'D'; // 一定先调用父类构造方法,再使用this变量。
}
}
contructor
方法没有这个限制class A {
create() {
console.log('class A.create')
}
}
class B extends A {
create() {
console.log('class B.create')
this.name = 12;
super.create();
}
}
var b = new B();
b.create()
class P {}
class C extends P {}
var c = new C();
console.log(c instanceof C) // true
console.log(c instanceof P) // true
console.log(Object.getPrototypeOf(C) === P) // true
prototype
也在子类实例对象的原型链上;class
表达式值就是个构造函数,它本身只是构造函数的语法糖。class
相当于JS自动帮开发创建了构造函数,正因为构造函数不是开发创建的,JS才能顺便做了一些规范限制(再也不用构造函数自己通过大写的名字告诉其他人:“hi, 看我的名字,请通过new
方式调用”)class
定义基于原型的继承更方便些。class Person{
constructor(name, age) {
this.name = name;
this.age = age;
}
say(){
console.log('I am ' + this.name + ' and age is ' + this.age);
}
}
// 等价于
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.say = function(){
console.log('I am ' + this.name + ' and age is ' + this.age);
}
class
继承class
继承逻辑还是比较复杂的:
new
方式调用,否则抛异常;大部分场景下可能不需要使用完整的功能,这里用构造函数简单模拟下。
function extend(subclass, superclass) {
"use strict";
function o() { this.constructor = subclass; }
o.prototype = superclass.prototype;
// 定义一个空函数,切记不要直接`new superclass()`
return (subclass.prototype = new o());
}
采用ES5的语法:
function extend(subclass, superclass) {
"use strict";
subclass.prototype = Object.create(superclass.prototype, {
contructor: { value: subclass }
})
return subclass;
}
function extend(subclass, superclass) {
"use strict";
subclass.prototype = Object.create(superclass.prototype, {
contructor: { value: subclass }
})
// 设置构造函数原型关系
if(0 && Object.setPrototypeOf) {
Object.setPrototypeOf(C, P);
} else {
C.__proto__ = P;
}
return subclass;
}
prototype
属性的,正确的做法应是保留原子类prototype
对象的属性function extend(subclass, superclass) {
"use strict";
var subClassPrototype = subclass.prototype;
subclass.prototype = Object.create(superclass.prototype, {
contructor: { value: subclass }
})
// 补充子类原prototype对象的属性
if(subClassPrototype === Object(subClassPrototype)) {
var props = Object.getOwnPropertyDescriptors(subClassPrototype);
Object.defineProperties(subclass.prototype, props);
}
// 设置构造函数原型关系
if(0 && Object.setPrototypeOf) {
Object.setPrototypeOf(C, P);
} else {
C.__proto__ = P;
}
return subclass;
}
new
时调用父类构造函数new
子类时,内部其实也调用了父类构造函数。babeljs怎么做的?
完整的功能参考下babeljs。
https://www.jianshu.com/p/4f07ef18b5d7
异步的,宏任务
https://www.html5rocks.com/zh/tutorials/workers/basics/
(JavaScript 深拷贝性能分析)[https://segmentfault.com/a/1190000013107871]
页面通过register
方法创建/更新ServiceWorkerRegistration
对象。注意不是直接操作ServiceWorker对象而是ServiceWorkerRegistration
对象。调用register方法的时候如果页面已经被SW控制了则是更新操作了
页 面:hi,浏览器。帮我向这个ServiceWorker发起注册申请。ServiceWorker信息是:scriptsURL, scope
浏览器:
页面可以多次调用register方法;
SW的scope最大范围是JS所在的目录,不能大于这个范围,register方法指定的scope只能比默认范围小;
scriptsURL必须是同域的【保证安全性】
源,scope, scriptURL三个维度唯一标示一个SW。
一个origin下同一个scope下面可以有多个sw么?
不可以,这是为保证【一个页面同一时刻只能受一个serviceWorker控制】。如果出现多个,则行为同更新sw流程。
是个Promise对象,当ServiceWorker对象变成activated后ready属性才被resolve,否则会一直等。
controllerchange事件的事件处理函数。当页面对应的ServiceWorker对象被新的ServiceWorker对象替换时会触发controllerchange
事件。第一次注册的ServiceWorker不会触发该事件。
如果每次都调整sw版本号发现会导致每次都作为新的sw,即执行update sw操作。
if ('serviceWorker' in navigator) {
// Use the window load event to keep the page load performant
window.addEventListener('load', () => {
navigator.serviceWorker.register('sw-fetch.js?v=' + Date.now()).then((registration) => {
var sw = null, state;
if(registration.installing) {
sw = registration.installing;
state = 'installing';
} else if(registration.waiting) {
sw = registration.waiting;
state = 'installed'
} else if(registration.active) {
sw = registration.active;
state = 'activated'
}
state && console.log(`sw state is ${state}`);
if(sw) {
sw.onstatechange = function() {
console.log(`sw state is ${sw.state}`);
}
}
});
});
}
当SW.js文件发生变更时修改避免缓存呢,修改url或者url追加版本参数都会导致注册新的ServiceWorker。
IOS12 软键盘收起问题页面会有空白区域,导致fix定位的元素热不偏移。
解决方案:
显示执行scrollTo,如:
document.querySelectorAll('input[type="text"]').forEach(function($input){
$input.addEventListener('blur', function() {
window.scrollTo(window.scrollX, window.scrollY) // 原位置滚动
})
})
function* name([param[, param[, ... param]]]) {
statements
}
格式上跟普通函数就多个了星号*
;
星号*
和function
,函数名之间可以没有空格,一般把星号和function
放一起。
生成器函数返回值是个生成器对象
vvar toString = Object.prototype.toString;
var gen = function* () {
console.log('begin')
yield 1;
yield 2;
return 5;
}
var genObj = gen();
console.log(typeof gen) // function
console.log(toString.call(gen)) // [object GeneratorFunction]
console.log(gen instanceof Function) // true
console.log(toString.call(genObj)) // [object Generator]
console.log(typeof genObj) // object
prototype
属性的。function
关键字。next/throw/return
)执行时才执行相关代码,遇到下一个yield
表达式时暂停(或者return
/throw
语句结束);var gen = function* () {
console.log('begin')
yield 1;
yield 2;
return 3;
}
var g = gen();
console.log('Generator created')
// [object Generator]
console.log(Object.prototype.toString.call(g))
console.log(g.next()) //
console.log(g.next())
yield
关键字把生成器函数分割成一段段可执行代码片段,调用next
方法时就执行其中的代码片段return
和throw
方法会立即终止,不会再次执行。return
语句表示生成器函数最终的值var gen = function* () {
yield 1;
yield 2;
return 3;
}
var g = gen();
console.log(g.next()) // {value: 1, done: false}
console.log(g.next()) // {value: 2, done: false}
console.log(g.next()) // {value: 3, done: true}
console.log(g.next()) // {value: undefined, done: true}
next
方法直接向外抛:var gen = function* () {
yield 1;
throw new Error('error occured')
yield 2;
return 5;
}
var g = gen();
console.log(g.next())
try {
console.log(g.next())
} catch(e) {
console.error(e)
}
console.log(g.next())
console.log(g.next())
yield*
写法表示遍历其他生成器对象function* anotherGenerator(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function* generator(i) {
yield i;
yield* anotherGenerator(i);
yield i + 10;
}
var gen = generator(10);
console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20
yield*
相当于:
function* generator(i) {
yield i;
let ag = anotherGenerator(i);
for(let val of ag) {
yield val ;
}
yield i + 10;
}
本质上yield*
后面可以是任意可迭代对象
var gen = function* () {
yield 1;
yield* [12, 123];
return 3;
}
var g = gen();
for(let val of g) {
console.log(val)
}
生成器对象是一个内部含有状态的对象,通过并且只能通过生成器函数创建。
生成器函数的返回值。
Object.prototype.toString
判断生成器函数和生成器对象类型function* gen(i) {
yield i.count + 1;
yield i.count + 2;
yield i.count + 3;
}
var a = {
count: 1
};
var g = gen(a)
Object.prototype.toString.call(gen) // [object GeneratorFunction]
Object.prototype.toString.call(g) // [object Generator]
// 可迭代对象
var arr = [...g]
console.log(arr) // [2, 3, 4]
console.log(Symbol.iterator in g) // true
GeneratorFunction
,Generator
;yield
表达式和return
语句用于定义生成器对象的内部状态;生成器对象本身就是个可迭代对象,但是只能一次性遍历。
function* gen() {
yield 1;
yield 3;
yield 5;
}
var g = gen();
// 第一次遍历
for(let num of g) {
console.log(num);
}
// 第二次遍历-
for(let num of g) {
console.log(num); // 不会再执行了
}
next
方法用于获取生成器对象下一个状态,即 yield
后面表达式的值 ;value
可以指定生成器函数里 yield
表达式的值 (注意区分“yield后面表达式的值”和yield
表达式本身的值)function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false},yield表达式的值为undefined
a.next() // Object{value:NaN, done:false},yield表达式的值为undefined
a.next() // Object{value:NaN, done:true},yield表达式的值为undefined
var b = foo(5);
b.next() // { value:6, done:false },yield表达式的值为undefined
b.next(12) // { value:8, done:false },yield表达式的值为12
b.next(13) // { value:42, done:true },yield表达式的值为13
next
方法的实参作为yield
表达式的值;
外部可以用作向生成器对象传递值,并告诉生成器对象继续执行。
next
方法的返回值就是yield
后面的表达式的值。
外部可以用作获取生成器对象内部的值
这种机制很重要,是实现异步同步化的关键。
让生成器函数在上次暂停的地方(或者函数开始处)立马结束,并返回指定的状态值(return方法实参):
{
done: true,
value: value // return方法的实参
}
return
方法可以多次调用,返回值的value属性是调用时的实参;next
方法的返回值逻辑不一样。return
方法时,生成器函数从上一次暂定的地方或者函数开始处就结束(即后面的代码不会再执行);var gen = function* () {
console.log('begin run')
throw new Error(1);;
yield 1;
yield 2;
return 5;
}
var g = gen();
// 立马结束,生成器函数没有执行代码的机会
console.log(g.return(1)) // {value: 1, done: true}
try-finally
语句块中finally
语句块中语句必须要执行,这跟return
方法立马结束有点冲突。return
方法时,如果上个yield
表达式在try
语句块中则return
方法会推迟到finally
代码块执行完再执行,并且可以修改返回值。即保证了finally
的语法规则不变(一定要执行,可以修改try/catch中的返回值)。function* foo() {
yield 1;
try {
yield 4;
} finally {
yield 5
}
}
var a = foo();
console.log(a.next()) // {value: 1, done: false}
console.log(a.next()) // {value: 4, done: false}
console.log(a.return(2)) // {value: 5, done: false} ,继续执行finally语句块(新的状态,修改原return的值)
console.log(a.next()) // {value: 2, done: true} , return方法指定的终态值
console.log(a.next()) // {value: undefined, true}
让生成器函数在上次暂停的地方(或者函数开始处)抛出指定异常(throw方法实参)。
throw
返回值的取值逻辑同next
方法;throw
方法视为抛出异常的next
方法。function* gen() {
yield 1;
yield 2;
try {
yield 3;
} catch(e) {
console.log('error catch in gen')
}
}
var gg = gen();
console.log(gg.next());
console.log(gg.next());
console.log(gg.next());
console.log(gg.throw('error occured'));
throw
方法相当于让生成器函数在上次暂停的地方(或者函数开始处)处抛一个异常,如果生成器函数没有捕获,则会向外抛,则相当于调用throw
处抛一个异常,也说明了函数开始处抛出的异常无法内部捕获。
function* gen() {
yield 1;
yield 2;
try {
yield 3;
} catch(e) {
console.log('error catch in gen')
}
}
var gg = gen();
console.log(gg.next());
console.log(gg.next());
console.log(gg.throw('error occured')); // 这个异常生成器函数没有捕获到
如果要捕获生成器没有捕获的异常,得在调用throw
处捕获:
function* gen() {
yield 1;
yield 2;
try {
yield 3;
} catch(e) {
console.log('error catch in gen')
}
}
var gg = gen();
console.log(gg.next());
console.log(gg.next());
try {
console.log(gg.throw('error occured'));
} catch(e) {
console.log('error catch in callee')
}
next/return/throw
方法都是用于恢复生成器函数执行,区别是:next
:继续执行;return
:终止执行,并返回指定的值;throw
:终止执行,并抛出指定的异常。这三个方法是外部控制生成器对象的执行,可用于实现异步逻辑同步化关键。
next()、throw()、return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。
用“语句替换”描述只是便于理解吧,但是有点误导。起码return
和throw
方法可以在函数开始处立马结束(生成器函数都没有执行)
var gen = function* () {
console.log('begin run')
yield 2;
return 5;
}
var g = gen();
console.log(g.return(1)) // 或者console.log(g.throw(1))都是的生成器函数没有执行
先看看下面输出是什么:
// Demo1
var a = 0
function* gen(x) {
a = a + (yield x);
console.log(3, a)
}
var g = gen(10)
var r = g.next();
console.log(1, r.value)
a++;
console.log(2, a)
g.next(5);
稍微调整代码,下面输出是什么:
// Demo2
var a = 0
function* gen(x) {
var b = yield x;
a += b;
console.log(3, a)
}
var g = gen(10)
var r = g.next();
console.log(1, r.value)
a++;
console.log(2, a)
g.next(5);
再稍微调整代码,下面输出是什么:
// Demo3
var a = 0
function* gen(x) {
a = (yield x) + a;
console.log(3, a)
}
var g = gen(10)
var r = g.next();
console.log(1, r.value)
a++;
console.log(2, a)
g.next(5);
执行下文会被暂存
遇到yield
表达式时,生成器函数暂停执行,退出调用栈,但是执行下文会被暂存。
恢复执行
暂存的执行上下文也被恢复。
回到上面的问题Demo1和Demo2的结果为啥不一样?
在Demo1
中语句 a = a + (yield x);
算术运算从左向右执行的,等执行到yield x
时变量a
已经参与运算了,暂停执行的时候,会存在临时变量里了。相当于:
function* gen(x) {
var tem = a;
a = tem + (yield x);
console.log(3, a)
}
移动设备屏幕大小,能看见内容的区域。
移动页面是从PC过渡过来的,移动端可见视口相对于PC可见视口是比较小的。PC页面放到移动端展示就会挤在一起了(如375px宽的屏幕肯定无法正常显示980px宽的PC页面),为了避免这种情况移动设备会用一个比较大的视图(一般980px)进行页面布局。这样布局视口的内容就不能完全展示了,要么进行左右滑动查看,要么缩小页面完全展示。浏览器选择了后者,即缩小布局视口展示在可见视口里(毕竟用户可以手动放大页面)。
这个layout viewport的宽度可以通过document.documentElement.clientWidth 来获取。
在基于REM布局中html的font-size计算就是按照布局尺寸算的。
就是可见视口。讲布局视口的宽度和可见视口宽度一致。本质上采用可见视口的宽度进行布局。
一直以为最小只能显示1px,但其实并不是这样的。
<meta name="viewport" content="width=device-width">
CSS布局像素,即PX。CSS里的1px并不是物理像素的1像素,那还得看独立像素比
。
1px的东西在屏幕上的大小与那些低分辨率的设备差不多
这里的1像素
指的就是1 CSS px
,CSS里px
也是相对单位(相对于物理像素)。
在不同的屏幕上,CSS像素所呈现的物理尺寸是一致的,而不同的是CSS像素所对应的物理像素具数是不一致的。在普通屏幕下1个CSS像素对应1个物理像素,而在Retina屏幕下,1个CSS像素对应的却是4个物理像素。
CSS像素、物理像素、逻辑像素、设备像素比、PPI、Viewport
一般指ddr > 1的屏幕
0.5px
问题1px
是CSS最小的单位,并且各个屏幕看起开擦不多。但是理论上在dpr >1
的屏幕中可以展示更细的线条,也是大家经常说到的0.5px
线条或则Retina屏幕1px
问题。
总体方式是:如果设备支持(限iOS 8及以上设备)更好,不支持再hack
目前只能利用JS,可以参考flexible 2.0
// detect 0.5px supports
if (dpr >= 2) {
var fakeBody = document.createElement('body')
var testElement = document.createElement('div')
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
docEl.appendChild(fakeBody)
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines')
}
docEl.removeChild(fakeBody)
}
viewport meta
+ rem
有坑,flexible都放不用这个了。
.hairlines li:after{
content: '';
position: absolute;
left: 0;
background: #000;
width: 100%;
height: 1px;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
只考虑了drp=2
都case,严谨点可以利用媒体查询处理多种drp
都场景(当然了代码量不小)
详细参考[前端]移动端Retina视网膜屏1px解决方案(H5))
肉鸡
关于Nodejs的标签有异步的,事件驱动的...
流是异步的
文档中关于流的介绍有这么一段话>A stream is an abstract interface for working with streaming data in Node.js
那什么是streaming data
呢?动态、持续生成的数据。见参考文档。
提供流数据操作的抽象api。
write, end方法第一个参数是数据块
可以分批执行缓存。跟uncork成对操作。
只有提供了消费或忽略数据的机制后,可读流才会产生数据。
有消费才有生成。
流是可逝去,有序的的 ?!
流是什么使用缓存的?
可写流的写入缓存满了后才会传给底层系统?
貌似不是的,显示调用cork方法才会强制写入内存中!?
流和管道
深入理解 Node.js Stream 内部机制
What exactly are streams?
NodeJs中的stream(流)- 基础篇
当流转换到 flowing 模式时会触发data事件,
在流处于处态时绑定data事件会切换流状态为flowing,进而触发data事件(这只是触发事件方式之一)。
流没有数据后触发
事件表明流有了新的动态:要么是有了新的数据,要么是到了流的尾部
?是表示流是否数据还是表示缓存池是否有数据
? 处理 'readable'事件可能造成吞吐量升高
返回目标流引用,可以构建管道链
数据流将被自动管理
var fs = require('fs')
var readable = fs.createReadStream('w.stream.text', {encoding: 'utf-8', highWaterMark: 7});
/* ----------- Event data & end----------------*/
// readable.on('data', (chunk) => {
// console.log(`1 接收到 ${chunk.length} 字节的数据。`);
// // readable.pause();
// // setTimeout(()=> {
// // readable.resume();
// // }, 2000)
// })
// readable.on('data', (chunk) => {
// console.log(`2 接收到 ${chunk.length} 字节的数据。`);
// })
/* ----------- Event readable & end ----------------*/
// readable.on('readable', () => {
// console.log(`${readable.read()}`);
// });
// readable.on('end', () => {
// console.log(`结束了`);
// })
/* -------------- 方法 pipe ------------- */
// var w1 = fs.createWriteStream('r.stream1.text')
// var w2 = fs.createWriteStream('r.stream2.text')
// readable.on('end', () => {
// console.log(`r-> 结束了`);
// })
// w1.on('end', () => {
// console.log(`w1-> 结束了`);
// })
// w2.on('end', () => {
// console.log(`w2-> 结束了`);
// })
// var pR1 = readable.pipe(w1)
// var pR2 = readable.pipe(w2)
// console.log(pR1 === w1)
// console.log(pR2 === w2)
/*----------------- 方法 read -------------------
1. 从缓存区里去取数据
2. 需要readable事件配合,要不然不知道啥时候缓存区有数据
*/
//console.log(readable.readableHighWaterMark)
// var count = 0;
// readable.on('readable', () => {
// let chunk;
// var insideCount = 0
// console.log(`Loop times: ${++count}`); // undefined ?
// while (null !== (chunk = readable.read(2))) {
// console.log(`\tRead loop times: ${++insideCount}`);
// console.log(`\tReceived ${chunk.length} bytes of data.`);
// }
// })
// readable.on('end', () => {
// console.log(`r-> 结束了`);
// })
/* ------------------ 方法 resume --------------------
1. flowing模式的流即使没有显示指定消费者,也会被消费,返回触发end事件
*/
readable.resume().on('end', () => {
console.log('Reached the end, but did not read anything.');
})
setTimeout(function() {
readable.on('data', (chunk) => {
console.log(chunk)
})
}, 4)
// 为什么消费流数据只是用一种方式?
// 两种mode下的readable事件和read方法
一直忙于搬砖,忽然觉得对Reactjs即属性,又陌生。从第一次写Reactjs相关的笔记到现在也过去几年了
React makes it painless to create interactive UIs.
擅长处理可交互的页面。
官方文档Docs, Blog
快速入门的使用教程
详细说明文档
React组件的状态是自己维护的,一般是在JS环境中调用setState方法更新的,但是对于表单元素他有自己的状态,数据的更改可能来自用户的输入。为保证状态的一致性,需要实现表单数据“双向绑定”。
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.