感谢学意语的闺蜜。
名字来自意语的 Mi Manchi
,翻译过来是 我想你......嗯,有点矫情。
取了后半部发音拼为 Mangi
,于是好巧不巧就成了 '吃' 的意思:yum:I like it ~
不过根据词法规则其实是 '你吃':smirk:。
喜欢打游戏,但是非常菜。喜欢玩最终幻想14,会跟你裂墙推荐卖力安利那种。
养着一只叫“皮皮”的猫,猫如其名,它真的很皮,但也超可爱。
先扔规范:
hidden
The generated box is invisible (fully transparent, nothing is drawn), but still affects layout. Furthermore, descendants of the element will be visible if they have 'visibility: visible'.
visibility
是用于规定被 html 元素生成的‘盒(box)’是否被渲染的。其值设置为 hidden
后,元素形成的盒仍会影响到布局但是该元素不可见,或者说没有被绘制。
这样就可以理解为什么鼠标点击相应区域不会触发事件了,因为点击没有实际地落在该元素上。
另一种使用 click()
的方法仍可以正常触发点击。
opacity
是css3增加的有关元素透明度的属性,它只决定以什么程度去混合这种渲染模式,而不会决定元素是否被渲染是否影响布局。
表现层状态转换(Representational State Transfer,缩写:REST)是Roy Thomas Fielding 博士于2000年在他的博士论文中提出来的一种万维网软件架构风格,目的是便于不同软件/程序在网络(例如互联网)中互相传递信息。表现层状态转换是根基于超文本传输协议(HTTP)之上而确定的一组约束和属性,是一种设计提供万维网络服务的软件构建风格。
-- 维基百科
需要注意的是,REST是设计风格而不是标准。
A CSS Module is a CSS file in which all class names and animation names are scoped locally by default.
组件的css样式和html结构必定是息息相关,合理的css命名能方便理解html的结构也方便维护和复用。
Block Element Modifier
.block {}
.block__element {}
.block--modifier {}
block-name__element-name--modifier-name
header
,contaienr
,menu
,checkbox
,input
,通常使用一个短名词来命名。menu item
,list item
,checkboc caption
,header title
disabled
,highlighted
,fixed
,size big
,color yellow
<form class="form form--theme-xmas form-simple">
<input class="form__input" type="text" />
<input class="form__submit form__submit--disabled" type="submit" />
</form>
.form {}
.form--theme-xmas {}
.form--simple {}
.form__input {}
.form__submit {}
.form__submit--disabled {}
The idea behind it is to divide the user interface into independent blocks. This makes interface development easy and fast even with a complex UI, and it allows reuse of existing code without copying and pasting.
.nav .nav__listItem .btn--orange {}
<a class="btn" href="http://www.jd.com">
<div class="nav__listItem">item one</div>
<div class="nav__listItem">item two</div>
</a>
不生成嵌套层级的写法
.block {
@at-root #{&}__element {}
@at-root #{&}--modifier {}
}
除了BEM,还有其他的CSS命名规则,如SMACSS、OOCSS,并不是说BEM就一定优于它们,而是它们包括BEM都意图于形成一套帮助理解css和保持其可维护的规则。
起源于BEM,使用驼峰命名和连字符区分后代元素和修饰符
.ComponentName {}
.ComponentName--modifierName {}
.ComponentName-descendantName {}
.ComponentName.is-someState {}
.ProductDetails {}
.ProductDetails-price {}
.ProductDetails-title--sale {}
完整名称: Object Oriented CSS,“面向对象CSS”
是比较早期的一种组织、编写、管理css的方案
两个主要的原则:
.mod {}
.inner {}
.talk {}
.talk .inner {}
完整名称:Scalable and Modular Architecture for CSS,“可扩展、模块化CSS架构”
样式被划分为5个部分:
在Layout、State、Module这三种中使用不同的前缀来作区分
如
layout-
、grid-
is-
module-
模块相关的元素使用一个 base name 作为前缀,如.exm
和.exm-caption
为模块编写样式时,避免使用id选择符和元素选择符
语义化命名
.example {}
.example-title {}
.example-contents {}
.example .is-important {} // 使用单独的状态
.example-important {} // 或者把模块和状态合并起来
.mt-1 { margin-top: 1px; }
.w-200 { width: 200px; }
Attribute Modules from CSS
<div button="large blue"></div>
<div am-button="large blue"></div>
如下样式:
.button {}
.button--large {}
.button--blue {}
可以被转换为
[am-button] {}
[am-button~='large'] {}
[am-button~='blue'] {}
如果觉得这么写太unusual了太激进了,你可以考虑如下写法
<div am-button am-button-large am-button-blue></div>
Flat hierarchy of selecters, Utility styles, Name-spaced components
层级扁平的选择器,通用样式,带有命名空间的组件
w100
-width: 100%
.sc-
-shop cart
命名方式和bootstrap相似始终使用“-”中划线分割,不使用扁平的层级,带有命名空间的嵌套
以alert组件为例
@alert-prefix-cls: ~"@{ant-prefix}-alert";
.@{alert-prefix-cls} {
&&-no-icon {}
&-icon {}
&-description {}
&-success {}
... ...
}
以button组件为例
@btn-prefix-cls: ~"@{ant-prefix}-btn";
.@{btn-prefix-cls} {
&-primary {}
&-ghost {}
&-dashed {}
&-danger {}
... ...
&-background-ghost {}
&-background-ghost&-primary {}
&-background-ghost&-danger {}
}
看到CSS十问这篇博客的一二问时突发奇想的一个脑回路,使用了writing-mode
这个平时超级少见的CSS属性。
好的地方是.outer
元素的宽高不必写死,没有用到位置偏移的属性,但是需要多用一层div
嵌套。
比较黑科技,但是兼容性不太好,常规还是通用方案比较稳。
<style>
.outer {
writing-mode: vertical-rl;
height: 200px;
width: 200px;
background-color: purple;
}
.inner {
writing-mode: horizontal-tb;
margin: auto 0;
width: 100%;
}
.content {
border: 1px solid red;
width: 100px;
margin: auto;
color: #fff;
}
</style>
<div class="outer">
<div class="inner">
<div class="content">fearless<br>speak now<br>red<br>1989</div>
</div>
</div>
效果:
原理:
一个符合W3C标准的浏览器会根据父容器的宽度进行计算,但是这个仅限于书写模式为横向的时候。因为在横向排版时,宽度“有迹可循”,可以把浏览器宽度作为参考,但是高度是不固定的,所以margin百分比值在计算时会参考父容器的宽度。当书写模式改为纵向,其计算参考便会变为父容器的高度了。
同样,margin:auto会受书写模式的影响。当书写模式为纵向时,margin:auto垂直方向是可以居中的。
参考:
最近在弄 github issue blog,开发时使用的个人 token 隔段时间就会过期,每次都要重新生成,虽然没几步操作,但是重复几次仍然很恼火。刚好借这个场景试一试谷歌的无头浏览器 puppeteer。
后话:(这里我误解是过期,实际上是我错误地将token作为内容提交了,github检测到这个危险行为后自动revoke了相关token,好孩子不要学我...)
npm install puppeteer
在没有科学上网的情况下会在下载 Chromium 时超时失败。
另外因为会使用 async/await
,所以 node 版本要在 7.6 以上。
开发时为了方便调试,可以在 launch
时设置 {headless: false}
启动 Chromium 窗口。
其他的按照需要查阅 pupeteer 的 API 文档使用就好,唯一比较需要注意的就是需要适当设置一下延时。
代码贴上来:
const puppeteer = require('puppeteer')
const TYPE_DELAY = 30
function delay (time = 3000) {
return new Promise((res, rej) => {
setTimeout(res, time)
})
}
(async () => {
const browser = await puppeteer.launch({headless: false})
const page = await browser.newPage()
// login
await page.goto('https://github.com/settings/tokens/new')
await page.type('#login_field', 'github account', {
delay: TYPE_DELAY
})
await page.type('#password', 'password', {
delay: TYPE_DELAY
})
let submitBtn = await page.$('form[action="/session"] [type="submit"]')
submitBtn.click()
await delay()
// token options
await page.type('#oauth_access_description', 'whatever you want', {
dealy: TYPE_DELAY
})
let checkUserEl = await page.$('[type="checkbox"][value="read:user"]')
checkUserEl.click()
let checkEmailEl = await page.$('[type="checkbox"][value="user:email"]')
checkEmailEl.click()
submitBtn = await page.$('form[action="/settings/tokens"] [type="submit"]')
submitBtn.click()
await delay()
// token
let token = await page.evaluate(() => {
let el = document.querySelector('.token')
return el.innerText
})
console.log(`github token is ${token}`)
browser.close()
})()
一旦一个 Promise 决议,不论是现在还是将来,下一个步骤总是相同的
既然 Promise 是通过 new Promise(..) 语法创建的,那你可能就认为可以通过 p instanceof Promise 来检查。但遗憾的是,这并不足以作为检查方法。识别 Promise(或者行为类似于 Promise 的东西)就是定义某种称为 thenable 的东西,将其定义为任何具有 then(..) 方法的对象和函数。我们认为,任何这样的值就是 Promise 一致的 thenable。
Promise 模式构建的可能最重要的特性:信任
即使是立即完成的 Promise(类似于 new Promise(function(resolve){ resolve(42); }))也无法被同步观察到。也就是说,对一个 Promise 调用 then(..) 的时候,即使这个 Promise 已经决议,提供给 then(..) 的回调也总会被异步调用
一个 Promise 决议后,这个 Promise 上所有的通过 then(..) 注册的回调都会在下一个异步时机点上依次被立即调用。这些回调中的任意一个都无法影响或延误对其他回调的调用
-- YDKJS
所谓"异步",简单说就是一个任务不是连续完成的,可以理解成该任务被人为分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。
...
相应地,连续的执行就叫做同步。由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。--阮一峰《ES6入门》
setTimeout(() => {
// statements
}, 1000)
var xhr = new XMLHttpRequest(),
method = "GET",
url = "https://developer.mozilla.org/";
xhr.open(method, url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
console.log(xhr.responseText);
}
};
xhr.send();
缺点:
大脑对于事情的计划方式是线性的、阻塞的、单线程的语义,但是回调表达异步流程的方式是非线性的、非顺序的,这使得正确推导这样的代码难度很大。
回调函数的调用控制交与第三方函数内部,无法保证回调函数一定会被正确调用。可能出现很多异常情况:所需参数传递错误、调用回调过早或过晚、调用回调次数太多或太少、吞掉可能出现的错误与异常等等。
回调函数是 JavaScript 异步的基本单元。但是随着 JavaScript 越来越成熟,对于异步编程领域的发展,回调已经不够用了。
...
我们需要一种更同步、更顺序、更阻塞的的方式来表达异步,就像我们的大脑一样。
也需要一个通用方案来解决信任问题。-- YDKJS
如果我们不把自己程序的 continuation 传给第三方,而是希望第三方给我们提供了解其任务何时结束的能力,然后由我们自己的代码来决定下一步做什么。这种范式就称为
Promise
。Promise 决议后就是外部不可变的值,我们可以安全地把这个值传递给第三方,并确信它不会被有意无意地修改。特别是对于多方查看同一个 Promise 决议的情况,尤其如此。一方不可能影响另一方对 Promise 决议的观察结果。 不可变性听起来似乎�一个学术话题,但实际上这是 Promise 设计中最基础和最重要的因素。
Promise 是一种封装和组合未来值的易于复用的机制。
-- YKDJS
最早的Promise是由社区首先提出和实现的,早期比较有名的有jQuery的Deferred对象、bluebird、Q等等。ES6也将Promise纳入语言标准,提供了原生的�Promise对象。
最新的规范是在2014年发布的promise/A+。
一个promise
指示一个异步操作的最终结果。与promise
交互的主要方法是通过它的then
方法,在then
方法中注册了接收promise
的最终值或是无法被完成原因的回调。
Promise/A+只专注于提供可操作的then
方法的规范。
规范中指出,ECMAScript语言规范中的Promise对象基于本规范还实现了许多额外的要求,也就是说我们可以自行实现一个完全遵守promise/A+规范但不必完全遵守ECMAScript语言规范的Promise,某种程度上说ES6里面的Promise也只是许多promise/A+规范实现的一种。
promise
: 带有按规范实现的then
方法的对象或函数thenable
: 定义了then
方法的对象或函数value
: 任何合法的JavaScript
值(包括undefined
、thenable
或是promise
),终值exception
: 使用throw
抛出的值reason
: 指示promise
被拒原因的值,拒因Promise
的状态
一个promise
必须是这三种状态之一:
pending
(进行中、等待中)fulfilled
(被完成、被执行)rejected
(被拒绝)状态的迁移:
pending
时,promise
的状态可以变到fulfilled
或是rejected
fulfilled
时,promise
的状态不可再变,同时必须持有一个不可变的value
(终值)rejected
时,promise
的状态不可再变,同时必须持有一个不可变的reason
(拒因)这里的不可变指的是恒等(即可用 ===
判断相等),但不意味着更深层次的不可变(如非基本类型值时,只要求引用地址相等)。
then
方法
一个promise
必须提供一个then
方法以访问其当前值、终值和据因。方法接受两个参数:
promise.then(onFulfilled, onRejected)
onFulfilled
、onRejected
均为可选参数,如果不是�函数类型,则必须被忽略
如果onFulfilled
是个函数:在promise被完成前不可被调用;在promise被完成后必须被调用,以promise的终值作为第一个参数;不能被多次调用。
如果onRejected
是个函数:在promise被拒绝前不可被调用;在promise被拒绝后必须被调用,以promise的据因作为第一个参数;不能被多次调用。
�调用时机
保证onFulfilled
、onRejected
在then
被调用的那轮事件循环之后的新执行栈中异步执行。
In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.
这一点可以使用宏任务(macro-task)机制如setTimeout
、setImmediate
,或微任务(micro-task)机制如MutationObserver
、process.nextTick
来实现。
调用要求
onFulfilled
和onRejected
必须被作为函数调用(即没有 this 值)(在严格模式中,函数this
的值为undefined
;在非严格模式中其为全局对象)
then
方法可以被同一个promise调用多次
onFulfilled
按注册顺序依次回调onRejected
按注册顺序依次回调then
方法必须返回一个promise对象
promise2 = promise1.then(onFulfilled, onRejected);
onFulfilled
、onRejected
返回一个值x,则运行下面决议promise
的过程:[[Resolve]](promise2, x)
onFulfilled
、onRejected
抛出一个异常e,则promise2
必须拒绝执行,并返回拒因eonFulfilled
不是函数且promise1
成功执行, promise2
必须成功执行并返回相同的值onRejected
不是函数且promise1
拒绝执行,promise2
必须拒绝执行并返回相同的拒因决议promise
的过程(即[[Resolve]](promise, x)
的具体实现)
x
与promise
相等,以TypeError
为拒因拒绝执行promisex
为Promise
的实例,则使promise
接受x
的状态
x
进行中,promise
也需保持进行中的状态直至x
被完成或拒绝x
被完成,用相同的值执行promise
x
被拒绝,用相同的据因拒绝promise
x
为函数或者对象
x.then
赋值给then
x.then
的值时抛出错误e
,则以e
为据因拒绝promise
如果then
是函数,将x
作为函数的作用域this
调用之。传递两个回调函数作为参数,第一个参数叫做resolvePromise
,第二个参数叫做rejectPromise
:
resolvePromise
以值y
为参数被调用,则运行[[Resolve]](promise, y)
rejectPromise
以据因r
为参数被调用,则以据因r
拒绝promise
resolvePromise
和rejectPromise
均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用then
方法抛出了异常e
:
resolvePromise
或rejectPromise
已经被调用,则忽略之e
为据因拒绝promise
如果then
不是函数,以x
为参数执行promise
x
不为对象或者函数,以x
为参数执行promise
龟速积累中...😳
elem.getBoundingClientRect.top - (offsetParent.getBoundingClientRect.top + offsetParent.style.borderTopWidth) - elem.style.marginTop
,这个处理方法和cssom的草案的offsettop略有不同,规范中的说明是:取目标元素的top border edge的Y轴坐标值减去相对父级的top padding edge的Y轴坐标值,所以实际上应该elem.getBoundingClientRect.top - (offsetParent.getBoundingClientRect.top + offsetParent.style.borderTopWidth)
更准确些if [ -f /etc/profile ]; then
PATH=""
source /etc/profile
fi
export NVM_DIR="$HOME/.nvm"
"/usr/local/opt/nvm/nvm.sh"
函数声明提升(function declaration hoisting):在执行代码之前会先读取函数声明,这意味着可以把函数声明放在调用它的语句后面。
能够创建函数再赋值给变量,也就能够把函数作为其他函数的值返回。
一个函数通过名字调用自身的情况
闭包是指有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式:在一个函数内部创建另一个函数。
当某个函数第一次被调用时,会创建一个执行环境及相应的作用域链,并把作用域链赋值给一个特殊的内部属性([[Scope]]
),然后,使用this
、arguments
和其他命名参数的值来初始化函数的活动对象。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,......直至作为作用域链重点的全局执行环境。
后台每个执行环境都有一个表示变量的对象——变量对象。全局变量对象始终存在,而局部环境的变量对象,只在函数执行的过程中存在。
举个栗子
function compare (value1, value2) {
return !!(value1 - value2)
}
在创建compare()
函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]
属性中。当调用compare()
函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]
属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行环境作用域链的前端。
对于栗子中的compare()
函数来说,其作用域链中包含两个变量对象:本地活动对象和全局变量对象。
显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
一般来说,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。
但是,总有意外嘛,闭包会有不同的情况。
在另一个函数内部定义的函数,会将包含函数(它的外部函数)的活动对象添加到它的作用域链中。
再举个栗子
function createCompareFunc (propName) {
return function (obj1, obj2) {
return !!(obj1[propName]- obj2[propName])
}
}
createCompareFunc
内部定义的匿名函数的作用域链中,实际上会包含外部函数createCompareFunc
的活动对象。
在匿名函数被从createCompareFunc
中返回后,它的作用域链被初始化为包含外部函数的活动对象和全局变量对象。这样匿名函数就可以访问在createCompareFunc
中定义的所有变量。
更重要的是,createCompareFunc
在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。
换句话说,当createCompareFunc
函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中,直到匿名函数被销毁后,createCompareFunc
的活动对象才会被销毁。
实际上都是规范里面关于引擎实现的概念,在实际代码执行的JS环境中不能被我们直接访问到,活动对象可以被认为是当前活跃的、被激活的变量对象,貘大的比喻:‘其实都是鞋,只不过是鞋架上的鞋和脚上的鞋的区别而已’。
(function () {
var now = Date.now()
console.log(now)
})()
因为JavaScript将function关键字当做一个函数声明的开始,而函数声明后面不能跟圆括号。所以要将其转换为函数表达式,在匿名函数外面包裹一层圆括号即可。
任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。
私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
把有权访问私有变量和私有函数的公有方法称为特权方法。
利用私有和特权成员,可以隐藏那些不应该被直接修改的数据。
安装 eslint、prettier、vetur 并进行配置,可以在保存时自动格式化代码内容。
注意项目中 .editorconfig
文件中的配置 editorconfig,此时需要利用 .prettierrc
重置规则。
Vetur 设置也需要相关改动:
html
formatter: js-beautify-html
js
formatter: prettier-eslint
vscode 安装 eslint、prettier、vetur 并进行配置,可以在保存时自动格式化代码内容。
注意 .editorconfig
文件中的配置 editorconfig,并且此时需要利用 .prettierrc
重置规则。
Vetur 设置也需要相关改动:
html
formatter: js-beautify-html
js
formatter: prettier-eslint
待定
待定
Vue:
React:
待定
JavaScript:
tabSize
设置为 4 使缩进显示更明显CSS:
BEM 命名法
待定
PingFangSC-Light
和 PingFangSC-Regular
,标注为 Regular
但视觉效果看上去像 Light
npm install -g @tarojs/cli@next
全局安装 Taro-clitaro init <project-name>
需要安装微信开发者工具,在项目目录中 npm run dev:weapp
后使用微信开发者工具打开 dist
目录
taro create --name xxx
创建页面功能无效,需要手动添加需要的页面文件夹层级及相关文件app.scss
不会被主动注入到小程序中,需要在 app.js
中手动 import
import
语法引入并使用H5
开发时 scss
转译有问题: 将 scss
样式写在 app.scss
中Taro
做了 html
元素的包装(web component)处理,在 h5
模式下,样式无法很好适用:观察包装容器的类名写特定的 hack 样式毕竟前版基于 React,Next 预览版虽然支持 Vue,小程序还可以,H5 就不太行了,结论:目前以 vue 为主体框架的多端开发体验尚不完整,暂时不可用
uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到 iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉)等多个平台。
官方文档相当完善,HBuilder 对 vue、小程序等相关功能集成已经比较傻瓜式了。
使用官方提供的 HBuilder
HBuilderX
-> 偏好设置 -> 源码视图,修改以下配置:
{
"editor.fontSize": 12, // 初始字体太大了
"editor.insertSpaces": true // 使用空格代替tab缩进
}
借助 mocker-api 和 mockjs 这两个工具,直接配置 devServer 的 before 选项即可。
请注意,由于 uni-app 本身配置文件的优先级很高,所以此时不可以配置
manifest.json
中的h5.devServer
,会导致 mock 失效
// vue.config.js
const webpackApiMocker = require('mocker-api')
module.exports = {
devServer: {
before(app) {
webpackApiMocker(app, path.resolve('./mock/index.js'))
}
}
}
// mock/index.js
const Mock = require('mockjs')
const Random = Mock.Random
const mock = Mock.mock
const proxy = {
'GET /api/user/list': mock({
'array|3': [
{
id: 1,
username: 'kenny',
sex: 'male'
}
]
}),
'POST /api/login/account': (req, res) => {
return res.json({
status: 'ok',
data: {
id: Random.id(),
userName: Random.cname(),
city: Random.city()
}
})
}
}
module.exports = proxy
<template/>
和 <block/>
进行 列表渲染 和 条件渲染(v-for
, v-if
),它们仅仅是包装元素,不会在页面中做任何渲染,只接受控制属性。【但是,实际开发block
不好用,不如template
或者直接在 view 上写指令】vue
开发的 注意事项components/banner/banner.vue
尺寸转换:菜单栏 HBuilderX
-> 偏好设置 -> 编辑器配置: px 转 rpx 比例,举例 375 宽度下设置为 0.5
静态资源目录:
static 目录下的 js 文件不会被编译,如果里面有 es6 的代码,不经过转换直接运行,在手机设备上会报错。
css、less/scss 等资源同样不要放在 static 目录下,建议这些公用的资源放在 common 目录下。
使用 npm 安装第三方库, 官方推荐优先插件市场
pages.json
的条件编译报错:每个条件内的语句块末行不能有逗号代码格式化:编辑区右键:重排代码格式
(Mac 下快捷键:option+shift+f
)
React 之前不熟悉,现学现卖...
.editorconfig:
indent_size = 4
.prettierrc:
{
"trailingComma": "none",
"tabWidth": 4,
"semi": false,
"singleQuote": true
}
.eslintrc
{
"jsx-quotes": ["error", "prefer-double"]
}
配套 UI: Taro-UI,组件与样式均可以按需引用
npm install taro-ui
在项目的 config/index.js
中配置
h5: {
esnextModules: ['taro-ui']
}
随后可以在命令行使用 taro doctor
命令检查依赖并安装,之后就可以在代码中引入并使用组件了。
import { AtButton } from 'taro-ui'
@import '~taro-ui/dist/style/components/button.scss';
与 uni-app 的 mock 方案相同,在 config/dev.js
文件中修改相关的 devServer 配置即可
{
h5: {
devServer: {
before(app) {
webpackApiMocker(
app,
path.resolve(__dirname, '../mock/index.js')
)
}
}
}
}
import Taro, { Component } from '@tarojs/taro'
中 Taro 的引入不可去掉,即使在同文件的代码中没有没使用taro create --name <page-name>
创建页面import { tlife } from '../../static/tlife-jssdk-2.0.0.min.js'
即可config/index.js
{
"designWidth": 375,
"deviceRatio": {
"375": 2 / 1,
"640": 2.34 / 2,
"750": 1,
"828": 1.81 / 2
}
}
本地配置的账号密码与实际不一致
控制面板 -> 用户账号 -> 凭据管理器 -> windows 凭据 -> 普通凭据:修改
class SimpleVue {
constructor (options) {
this.data = options.data
this.initData()
this.observeData()
// 为属性添加watcher,这个示例没有处理编译方法,但是模板的实时更新应该也是这样绑定了一个回调
let setContentFunc = () => {
options.el.innerHTML = `foo is: ${this.foo}, bar is: ${this.bar}`
}
new Watcher(this, 'foo', setContentFunc)
setContentFunc()
}
initData () {
let data = this.data
let vm = this
for (let key in data) {
// 把对data的数据访问代理到当前实例对象本身上
Object.defineProperty(vm, key, {
configurable: true,
enumurable: true,
get () {
return vm.data[key]
},
set (val) {
vm.data[key] = val
}
})
}
}
observeData () {
let data = this.data
let vm = this
for (let key in data) {
// 数据绑定
defineReactive(data, key)
}
}
}
function defineReactive (obj, key) {
// 这里用闭包保存了值
let value = obj[key]
let dep = new Dep()
Object.defineProperty(obj, key, {
configurable: true,
enumurable: true,
get () {
if (Dep.target) {
// 收集依赖,这里收集的是watcher,可能会有很多watcher
dep.depend(Dep.target)
}
return value
},
set (val) {
value = val
// 派发更新
dep.notify()
}
})
}
class Dep {
constructor () {
this.subs = []
}
depend (watcher) {
this.subs.push(watcher)
}
notify () {
for (let sub of this.subs) {
sub.run()
}
}
}
class Watcher {
// watcher里面保存了对象、键还有watch了这个键的回调
constructor (vm, key, cb) {
this.vm = vm
this.key = key
this.cb = cb
this.value = this.get()
}
run () {
this.cb()
}
get () {
// 将这个watcher添加的dep的依赖收集里面去
// 因为这里是一个get操作,而且又设置了Dep.target所以能够收集到
// 见defineReactive的get那里
Dep.target = this
let value = this.vm[this.key]
Dep.target = null
return value
}
}
let sv = new SimpleVue({
el: document.querySelector('#app'),
data: {
foo: 'foo',
bar: 'bar'
}
})
Vue的DOM更新是异步的
可能你还没有注意到,Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会一次推入到队列中。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MutationObserver,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。
nextTick实际指向的是一个立即执行函数返回的结果,该结果是一个可执行的方法。
该立即执行函数内部持有几个变量:callbacks
, pending
, timerFunc
, nextTickHandler
在这个IIFE执行过程中,timerFunc
将被视情况处理为不同类型的函数
源码注释:虽然MutationObserver
有更广泛的支持,但是在ios9.3.3及以上版本中会有严重问题,所以降级顺序为:
Promise.resolve().then()
MutationObserver.observe()
setTimeout(fn, 0)
在结束一系列初始化和timerFunc
的设置后,返回实际的nextTick指向的方法如下
timerFunc
为Microtask队列添加nextTickHandler
,这样做使同一Macrotask中的回调函数均被收集到callbacks
数组中,在下一个Macrotask执行前被统一执行nextTickHandler
中,false掉pending
,将callbacks置回空数组,回调按照推入顺序逐一被执行说人话就是:收集当前任务队列中所有的nextTick callbacks,统一放到当前任务队列结束下一任务队列开始之前执行。
由于本人能力有限,理解和描述难免会有偏差和错误,希望大家不吝指正。
(本文提及webpack
的版本为4.29.x,其依赖于tapable
的版本是1.1.0)
刚才提及的tapable
这个名字应该会令大部分同学觉得陌生,但webpack
这个目前使用率最高的打包工具就人人皆知了吧。
tapable
实际上也是webpack
团队的项目,它暴露了很多钩子类,并且可以被用于给插件创建钩子。
我认为tapable
可以被称为webpack
重要的基石之一,看过webpack
解析博文的同学应该知道Compiler
和Compilation
对象在其中的重要作用,而这二者都直接继承自tapable
中提供的Tapable
对象,并且直接使用了tapable
提供的钩子对象的API。
(当然最新版本的tapable
选择不再对外提供Tapable
而仅暴露钩子类,猜测可能是针对webpack5.0
做了优化和开发)
—— 什么?你说你没看过相关解析,也不知道Compiler
和Compilation
他俩做了啥?那少年你大概有点out了...
—— 什么?平时需求多太忙了没空看?好吧,我原谅你...
...还好我有一丢丢准备,不过会很简略,将就着看吧
在webpack
启动时,会一次性地初始化一个编译器Compiler
的实例,在这个实例中包含了所有可自定义操作的配置(在插件自定义的子编译流程中,可以通过这个Compiler
实例拿到主环境的所有信息),Compiler
实例根据配置生成Compilation
实例对象,Compilation
实例代表了一次单一版本构建和生成编译资源的过程,其中包含了编译资源的信息,这个信息是个对象,以键值对的形式记录了所有静态资源的相关信息,最终由Compilation
实例生成bundle。
这一节拆开分成事件流和插件系统两部分来讲。
先说插件系统【大喘气】背后的设计原理
控制反转(Inversion of Control,缩写为IoC,也被称为依赖注入,Dependency Injection,DI)是面向对象编程中的一种设计原则,可以用来降低代码之间的耦合度,提高组件的可移植性。
传统应用架构中,低层组件设计成被高层组件使用,我们常在高层组件中使用new关键字来直接使用低层组件,这种结构可以逐步构建一个复杂的应用,但是高层组件直接依赖于低层组件去实现,将会限制高层次组件重用的可能性。
控制反转将低层组件从高层组件中分离,为其定义或持有高层组件所必须的行为和服务的接口,由公共的外部容器在运行时将高层组件作为某种依赖关系动态的注入低层组件中。传统结构中高层组件对低层组件的直接使用变成低层组件针对高层接口完成具体实现,此时是低层次组件直接依赖于高层次组件,控制权被反转。
看图说话更清楚~
示例图
高层对象A依赖于底层对象B的实现:
把高层对象A对底层对象的需求抽象为接口A,底层对象实现了接口A:
如果还是觉得好像窗户纸还没被捅破,就再举个栗子好了~绝对生动形象!
情景:弯姐想去约会
class Girl {
construtor({name = '佚名', age = 0}) {
this.name = name
this.age = age
}
date() {
// 想约会需要一个男同学哇
// 这个男同学怎么获取呢
}
}
let 弯姐 = new Girl({name: '刘弯', age: 16})
弯姐.date()
Round 1:弯姐需要自己找,而且弯姐得对人家知根知底还要负一辈子责任,emmm...这样不好
class Girl {
construtor({name = '佚名', age = 0}) {
this.name = name
this.age = age
}
date() {
let boy = this.boy = new Boy()
boy.date()
}
}
let 弯姐 = new Girl({name: '刘弯', age: 16})
弯姐.date()
Round 2:兵哥和佳宇给弯姐介绍认识的男生,但是这种情况就会导致弯姐想约会就总得缠着他俩,他俩得一直为弯姐介绍,太辛苦了,也不好
class Girl {
construtor({name = '佚名', age = 0}) {
this.name = name
this.age = age
}
date() {
let boy = this.boy = 兵哥.introduceBoy() || 佳宇.introduceClassmate('boy')
boy.date()
}
}
let 弯姐 = new Girl({name: '刘弯', age: 16})
弯姐.date()
Round 3:弯姐不想自己找了,她这次坐等,告诉大家她需要一个男同学和她去约会,而且要求这个男生对姑娘得主动点,让大家帮她安排
class Girl {
construtor({name = '佚名', age = 0}) {
this.name = name
this.age = age
}
date() {
// 逛街吃饭看电影...
}
}
class Boy {
construtor({name = '佚名', age = 0}) {
this.name = name
this.age = age
}
dateWith(girl) {
girl.date()
}
}
let 弯姐 = new Girl({name: '刘弯', age: 16})
let 小哥哥 = new Boy({name: '小哥哥', age: 18})
小哥哥.dateWith(弯姐)
对于插件系统这个模式来讲,控制反转做到的则是在运行时动态地为当前插件提供其需求的主依赖对象,并且做到二者间的解耦。
回头看webpack,人家这方面做得非常好【但是会有负作用:主流程对插件注册、调用是分离的,导致插件的完整生命流程被切碎,源码阅读困难度+++】
其实webpack
本质上就是一个插件系统。为什么这么说?前面我提到了Compiler
和Compilation
都直接继承自tapable
中提供的Tapable
对象并且直接使用了其他钩子对象。
而在webpack
内部就定义了大量了自有插件,供Compiler
和Compilation
使用。
这一点从源码看可能会更清晰一些,如果翻看webpack
的lib/webpackOptionsApply.js
这个文件会发现五百多行代码里四百行都是插件的声明和注册 😂
Q: 这一小节的标题是webpack的事件流插件系统,这个事件流是啥样的?
A: Compiler Hooks-依照事件注册顺序
Compilation Hooks-依照事件注册顺序
lib/Compiler.js
和lib/Compilation.js
的源码稍微瞅瞅~
Q: webpack
的插件是怎么运行起来的呢?跟事件流有什么关系?
A: 看tapable
的API去!
tapable
部分钩子解析(只准备梳理Hook、HookCodeFactory,和SyncBailHook、SyncHook、AsyncParallelHook、AsyncSeriesHook这几种被用到的钩子)
Hook和HookCodeFactory,这俩货是其他所有钩子对象的基础
简单情况下:生成的SyncHook代码
使用情景
let hook = new SyncHook(['name'])
hook.tap('second', (name) => {console.log(name)})
hook.tap({
name: 'first',
before: 'second'
}, (name) => {console.log(name)})
hook.call('first name')
setup中设置了_x“隐私”属性
instance._x = options.taps.map(t => t.fn);
生成执行代码前,在_insert方法中利用taps的before和stage的值进行比较并排序(决定执行顺序)
生成的执行代码
function (name) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(name);
var _fn1 = _x[1];
_fn1(name);
}
SyncBailHook
function (name) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _result0 = _fn0(name);
if (_result0 !== undefined) {
return _result0;;
} else {
var _fn1 = _x[1];
var _result1 = _fn1(name);
if (_result1 !== undefined) {
return _result1;;
} else {}
}
}
注意需要使用tapAsync
AsyncParallelHook
function (name, _callback) {
"use strict";
var _context;
var _x = this._x;
do {
var _counter = 2;
var _done = () => {
_callback();
};
if (_counter <= 0) break;
var _fn0 = _x[0];
_fn0(name, _err0 => {
if (_err0) {
if (_counter > 0) {
_callback(_err0);
_counter = 0;
}
} else {
if (--_counter === 0) _done();
}
});
if (_counter <= 0) break;
var _fn1 = _x[1];
_fn1(name, _err1 => {
if (_err1) {
if (_counter > 0) {
_callback(_err1);
_counter = 0;
}
} else {
if (--_counter === 0) _done();
}
});
} while (false);
}
AsyncSeriesHook
function (name, _callback) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(name, _err0 => {
if (_err0) {
_callback(_err0);
} else {
var _fn1 = _x[1];
_fn1(name, _err1 => {
if (_err1) {
_callback(_err1);
} else {
_callback();
}
});
}
});
}
Q: 外部声明的插件是什么时候被注册的呢?
A: 以最常见的导出单一对象的配置方式说明,在初始化Compiler
对象之后较早的时候就被注册了。
webpack.js:37
options = new WebpackOptionsDefaulter().process(options);
compiler = new Compiler(options.context);
compiler.options = options;
new NodeEnvironmentPlugin().apply(compiler);
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
compiler.options = new WebpackOptionsApply().process(options, compiler);
Q: 他们怎么去兼顾自有插件和第三方插件的呢?
A: 亲,乖乖再看一遍上面的内容哦 【手动微笑
待续...
啊,只是一些突然的小想法,不足成篇,先放在这里吧:relaxed:
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.