Git Product home page Git Product logo

blog's Introduction

有关 Mangi

感谢学意语的闺蜜。

名字来自意语的 Mi Manchi,翻译过来是 我想你......嗯,有点矫情。

取了后半部发音拼为 Mangi,于是好巧不巧就成了 '吃' 的意思:yum:I like it ~

不过根据词法规则其实是 '你吃':smirk:。

碎碎念

喜欢打游戏,但是非常菜。喜欢玩最终幻想14,会跟你裂墙推荐卖力安利那种。

养着一只叫“皮皮”的猫,猫如其名,它真的很皮,但也超可爱。

blog's People

Contributors

mangidu avatar

Watchers

 avatar  avatar  avatar  avatar

blog's Issues

vscode控制vue项目代码风格的一些配置

安装 eslint、prettier、vetur 并进行配置,可以在保存时自动格式化代码内容。

注意项目中 .editorconfig 文件中的配置 editorconfig,此时需要利用 .prettierrc 重置规则。

Vetur 设置也需要相关改动:

  • html formatter: js-beautify-html
  • js formatter: prettier-eslint

RESTful

表现层状态转换(Representational State Transfer,缩写:REST)是Roy Thomas Fielding 博士于2000年在他的博士论文中提出来的一种万维网软件架构风格,目的是便于不同软件/程序在网络(例如互联网)中互相传递信息。表现层状态转换是根基于超文本传输协议(HTTP)之上而确定的一组约束和属性,是一种设计提供万维网络服务的软件构建风格。
-- 维基百科

需要注意的是,REST是设计风格而不是标准。

  • 资源是由URI来指定。
  • 对资源的操作包括获取、创建、修改和删除资源,这些操作正好对应HTTP协议提供的GET、POST、PUT和DELETE方法。
  • 通过操作资源的表现形式来操作资源。
  • 资源的表现形式则是XML或者HTML,取决于读者是机器还是人,是消费web服务的客户软件还是web浏览器。当然也可以是任何其他的格式,例如JSON。

从另一个角度了解Webpack:一个事件流插件系统

由于本人能力有限,理解和描述难免会有偏差和错误,希望大家不吝指正。

(本文提及webpack的版本为4.29.x,其依赖于tapable的版本是1.1.0)

刚才提及的tapable这个名字应该会令大部分同学觉得陌生,但webpack这个目前使用率最高的打包工具就人人皆知了吧。
tapable实际上也是webpack团队的项目,它暴露了很多钩子类,并且可以被用于给插件创建钩子。
我认为tapable可以被称为webpack重要的基石之一,看过webpack解析博文的同学应该知道CompilerCompilation对象在其中的重要作用,而这二者都直接继承自tapable中提供的Tapable对象,并且直接使用了tapable提供的钩子对象的API。
(当然最新版本的tapable选择不再对外提供Tapable而仅暴露钩子类,猜测可能是针对webpack5.0做了优化和开发)

—— 什么?你说你没看过相关解析,也不知道CompilerCompilation他俩做了啥?那少年你大概有点out了...
—— 什么?平时需求多太忙了没空看?好吧,我原谅你...

...还好我有一丢丢准备,不过会很简略,将就着看吧

webpack构建流程简述

webpack启动时,会一次性地初始化一个编译器Compiler的实例,在这个实例中包含了所有可自定义操作的配置(在插件自定义的子编译流程中,可以通过这个Compiler实例拿到主环境的所有信息),Compiler实例根据配置生成Compilation实例对象,Compilation实例代表了一次单一版本构建和生成编译资源的过程,其中包含了编译资源的信息,这个信息是个对象,以键值对的形式记录了所有静态资源的相关信息,最终由Compilation实例生成bundle。

webpack的事件流插件系统

这一节拆开分成事件流和插件系统两部分来讲。

先说插件系统【大喘气】背后的设计原理

亲,控制反转再多了解一下?

控制反转(Inversion of Control,缩写为IoC,也被称为依赖注入,Dependency Injection,DI)是面向对象编程中的一种设计原则,可以用来降低代码之间的耦合度,提高组件的可移植性。

传统应用架构中,低层组件设计成被高层组件使用,我们常在高层组件中使用new关键字来直接使用低层组件,这种结构可以逐步构建一个复杂的应用,但是高层组件直接依赖于低层组件去实现,将会限制高层次组件重用的可能性。

控制反转将低层组件从高层组件中分离,为其定义或持有高层组件所必须的行为和服务的接口,由公共的外部容器在运行时将高层组件作为某种依赖关系动态的注入低层组件中。传统结构中高层组件对低层组件的直接使用变成低层组件针对高层接口完成具体实现,此时是低层次组件直接依赖于高层次组件,控制权被反转。

看图说话更清楚~

示例图
高层对象A依赖于底层对象B的实现:
高层对象A依赖于底层对象B的实现

把高层对象A对底层对象的需求抽象为接口A,底层对象实现了接口A:
把高层对象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的插件系统本质?

其实webpack本质上就是一个插件系统。为什么这么说?前面我提到了CompilerCompilation都直接继承自tapable中提供的Tapable对象并且直接使用了其他钩子对象。
而在webpack内部就定义了大量了自有插件,供CompilerCompilation使用。

这一点从源码看可能会更清晰一些,如果翻看webpacklib/webpackOptionsApply.js这个文件会发现五百多行代码里四百行都是插件的声明和注册 😂

事件流又是个什么情况?

Q: 这一小节的标题是webpack的事件流插件系统,这个事件流是啥样的?

A: Compiler Hooks-依照事件注册顺序
Compilation Hooks-依照事件注册顺序
lib/Compiler.jslib/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: 亲,乖乖再看一遍上面的内容哦 【手动微笑

更多分析

待续...

参考

另一个脑回路的水平垂直居中

看到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>

效果:

image

原理:

一个符合W3C标准的浏览器会根据父容器的宽度进行计算,但是这个仅限于书写模式为横向的时候。因为在横向排版时,宽度“有迹可循”,可以把浏览器宽度作为参考,但是高度是不固定的,所以margin百分比值在计算时会参考父容器的宽度。当书写模式改为纵向,其计算参考便会变为父容器的高度了。

同样,margin:auto会受书写模式的影响。当书写模式为纵向时,margin:auto垂直方向是可以居中的。

兼容性:
image

参考:

简单实现一个数据双向绑定

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'
  }
})

`visibility: hidden;` 和 `opacity: 0;` 的区别

先扔规范:

visibility

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

opacity 是css3增加的有关元素透明度的属性,它只决定以什么程度去混合这种渲染模式,而不会决定元素是否被渲染是否影响布局。

有关Promise

捋捋Promise

�一些引用

一旦一个 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入门》

在 Promise 之前:回调函数

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

Promise登场

promise的**

如果我们不把自己程序的 continuation 传给第三方,而是希望第三方给我们提供了解其任务何时结束的能力,然后由我们自己的代码来决定下一步做什么。这种范式就称为Promise

Promise 决议后就是外部不可变的值,我们可以安全地把这个值传递给第三方,并确信它不会被有意无意地修改。特别是对于多方查看同一个 Promise 决议的情况,尤其如此。一方不可能影响另一方对 Promise 决议的观察结果。 不可变性听起来似乎�一个学术话题,但实际上这是 Promise 设计中最基础和最重要的因素。

Promise 是一种封装和组合未来值的易于复用的机制。

-- YKDJS

社区推动

最早的Promise是由社区首先提出和实现的,早期比较有名的有jQuery的Deferred对象、bluebird、Q等等。ES6也将Promise纳入语言标准,提供了原生的�Promise对象。

最新的规范是在2014年发布的promise/A+

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值(包括undefinedthenable或是promise),终值
  • exception: 使用throw抛出的值
  • reason: 指示promise被拒原因的值,拒因

要求摘要&整理

  1. Promise的状态

    一个promise必须是这三种状态之一:

    • pending(进行中、等待中)
    • fulfilled(被完成、被执行)
    • rejected(被拒绝)

    状态的迁移:

    1. pending时,promise的状态可以变到fulfilled或是rejected
    2. fulfilled时,promise的状态不可再变,同时必须持有一个不可变value(终值)
    3. rejected时,promise的状态不可再变,同时必须持有一个不可变reason(拒因)

    这里的不可变指的是恒等(即可用 === 判断相等),但不意味着更深层次的不可变(如非基本类型值时,只要求引用地址相等)。

  2. then方法

    一个promise必须提供一个then方法以访问其当前值、终值和据因。方法接受两个参数:

    promise.then(onFulfilled, onRejected)
    1. onFulfilledonRejected均为可选参数,如果不是�函数类型,则必须被忽略

    2. 如果onFulfilled是个函数:在promise被完成前不可被调用;在promise被完成后必须被调用,以promise的终值作为第一个参数;不能被多次调用。

    3. 如果onRejected是个函数:在promise被拒绝前不可被调用;在promise被拒绝后必须被调用,以promise的据因作为第一个参数;不能被多次调用。

    4. �调用时机

      保证onFulfilledonRejectedthen被调用的那轮事件循环之后的新执行栈中异步执行。

      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)机制如setTimeoutsetImmediate,或微任务(micro-task)机制如MutationObserverprocess.nextTick来实现。

    5. 调用要求

      onFulfilledonRejected必须被作为函数调用(即没有 this 值)(在严格模式中,函数this的值为undefined;在非严格模式中其为全局对象)

    6. then方法可以被同一个promise调用多次

      • 当promise被完成时,所有onFulfilled按注册顺序依次回调
      • 当promise被拒绝时,所有onRejected按注册顺序依次回调
    7. then方法必须返回一个promise对象

      promise2 = promise1.then(onFulfilled, onRejected);
      • 如果onFulfilledonRejected返回一个值x,则运行下面决议promise的过程:[[Resolve]](promise2, x)
      • 如果onFulfilledonRejected抛出一个异常e,则promise2必须拒绝执行,并返回拒因e
      • 如果onFulfilled不是函数且promise1成功执行, promise2必须成功执行并返回相同的值
      • 如果onRejected不是函数且promise1拒绝执行,promise2必须拒绝执行并返回相同的拒因
  3. 决议promise的过程(即[[Resolve]](promise, x)的具体实现)

    1. 如果xpromise相等,以TypeError为拒因拒绝执行promise
    2. 如果xPromise的实例,则使promise接受x的状态
      • 如果x进行中,promise也需保持进行中的状态直至x被完成或拒绝
      • 如果x被完成,用相同的值执行promise
      • 如果x被拒绝,用相同的据因拒绝promise
    3. 如果x为函数或者对象
      1. x.then赋值给then
      2. 如果取x.then的值时抛出错误e ,则以e为据因拒绝promise
        1. 如果then是函数,将x作为函数的作用域this调用之。传递两个回调函数作为参数,第一个参数叫做resolvePromise,第二个参数叫做rejectPromise:

          • 如果resolvePromise以值y为参数被调用,则运行[[Resolve]](promise, y)
          • 如果rejectPromise以据因r为参数被调用,则以据因r拒绝promise
          • 如果resolvePromiserejectPromise均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
          • 如果调用then方法抛出了异常e
            • 如果resolvePromiserejectPromise已经被调用,则忽略之
            • 否则以e为据因拒绝promise
        2. 如果then不是函数,以x为参数执行promise

      3. 如果x不为对象或者函数,以x为参数执行promise

测试

练习题

参考�

清单

  • 杀死一只知更鸟
  • 指环王
  • 霍比特人
  • 认知与设计(理解UI设计准则)

电影

  • Begin again (2020.4.25)
  • 指环王三部(2020.5.4)
  • 环形使者(2020.5.22)
  • 霍比特人三部
  • 教父三部
  • 十二宫 (2020.5.29)

音乐剧

  • 德扎 (2020.3)
  • 1789
  • 亚瑟王

纪录片

  • 手术两百年
  • 蔚蓝之境

动漫

  • Banana fish(2020.5.14)

puppeteer 试水:获取 Github personal access token

最近在弄 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()
})()

Windows踩坑

git remote: HTTP Basic: Access Denied

本地配置的账号密码与实际不一致

控制面板 -> 用户账号 -> 凭据管理器 -> windows 凭据 -> 普通凭据:修改

windows-build-tools离线安装卡住

函数表达式高程笔记

函数声明提升(function declaration hoisting):在执行代码之前会先读取函数声明,这意味着可以把函数声明放在调用它的语句后面。

能够创建函数再赋值给变量,也就能够把函数作为其他函数的值返回。

递归

一个函数通过名字调用自身的情况

闭包

闭包是指有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式:在一个函数内部创建另一个函数。

函数第一次被调用时发生了什么

当某个函数第一次被调用时,会创建一个执行环境及相应的作用域链,并把作用域链赋值给一个特殊的内部属性([[Scope]]),然后,使用thisarguments和其他命名参数的值来初始化函数的活动对象。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,......直至作为作用域链重点的全局执行环境。

后台每个执行环境都有一个表示变量的对象——变量对象。全局变量对象始终存在,而局部环境的变量对象,只在函数执行的过程中存在。

举个栗子

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环境中不能被我们直接访问到,活动对象可以被认为是当前活跃的、被激活的变量对象,貘大的比喻:‘其实都是鞋,只不过是鞋架上的鞋和脚上的鞋的区别而已’。

用立即执行函数(IIFE)来模拟私有的块级的作用域

(function () {
    var now = Date.now()
    console.log(now)
})()

因为JavaScript将function关键字当做一个函数声明的开始,而函数声明后面不能跟圆括号。所以要将其转换为函数表达式,在匿名函数外面包裹一层圆括号即可。

私有变量

任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。

私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。

把有权访问私有变量和私有函数的公有方法称为特权方法

利用私有和特权成员,可以隐藏那些不应该被直接修改的数据。

扩展阅读

实用的Stack Overflow问题收集(不止SO)

龟速积累中...😳

FE

  • 命中火狐浏览器的 CSS HACK
  • 谷歌和火狐浏览器offset计算差异的问题
    • 这个回答只是提供了最粗暴直接的办法:用jQuery
    • 但实际上1.12版本的jQuery中的解决思路是:非根元素非fixed元素elem,获取到其相对定位的父级元素offsetParent,伪代码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) 更准确些
  • NodeJs: exec与spawn的差异
    • exec返回buffer(默认size是200k),spawn返回stream
    • exec是同步的,spawn是异步的
    • 需要简单状态结果返回时用exec,大量数据的过程处理等用spawn
  • 什么是JSON安全的数据

工具使用

【WIP】多端编译打包工具 踩坑

开发规范及约定

代码风格

IDE 准备工作

Vue

vscode 安装 eslint、prettier、vetur 并进行配置,可以在保存时自动格式化代码内容。

注意 .editorconfig 文件中的配置 editorconfig,并且此时需要利用 .prettierrc 重置规则。

Vetur 设置也需要相关改动:

  • html formatter: js-beautify-html
  • js formatter: prettier-eslint
React

待定

风格约定

待定

Vue:

eslint-vue-config

React:

待定

JavaScript:

  • tabSize 设置为 4 使缩进显示更明显
  • js 中一律使用 ' (单引号),发生嵌套使用 ` (点号)
  • 句末不加分号

CSS:

BEM 命名法

TypeScript 支持

待定

设计相关

  • 需要确定一下设计稿的字体 PingFangSC-LightPingFangSC-Regular,标注为 Regular 但视觉效果看上去像 Light

Taro Next【vue】

开发前准备

  1. 命令行执行 npm install -g @tarojs/cli@next 全局安装 Taro-cli
  2. taro init <project-name>

微信小程序

需要安装微信开发者工具,在项目目录中 npm run dev:weapp 后使用微信开发者工具打开 dist 目录

开发注意事项

多端同步开发及调试

多端同步调试

Taro 问题

已解决

  • taro create --name xxx 创建页面功能无效,需要手动添加需要的页面文件夹层级及相关文件
  • 设计稿尺寸相关 设计稿及尺寸单位,注意官方文档中的换算是错的,反了,根据微信小程序尺寸文档,在 375 宽度下:1rpx = 0.5px
  • 生成的 app.scss 不会被主动注入到小程序中,需要在 app.js 中手动 import
  • 图片资源引用问题:如 Taro 文档所说,使用 import 语法引入并使用
  • 样式的单位问题 rpx
  • 小程序的 tabbar 和 h5 的 navBar 怎么处理: 条件编译

可绕过

  • H5 开发时 scss 转译有问题: 将 scss 样式写在 app.scss
  • Taro 做了 html 元素的包装(web component)处理,在 h5 模式下,样式无法很好适用:观察包装容器的类名写特定的 hack 样式

待解决

  • 小程序开发多页面时怎么固定开发时预览的页面,每次都要手动点来点去,很烦
  • 组件转原生 H5 不太行,很多都支持不好,目前已尝试的 swiper 与 chooseImage 都不好用

总结

毕竟前版基于 React,Next 预览版虽然支持 Vue,小程序还可以,H5 就不太行了,结论:目前以 vue 为主体框架的多端开发体验尚不完整,暂时不可用

Uni-app【vue】

uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到 iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉)等多个平台。

官方文档相当完善,HBuilder 对 vue、小程序等相关功能集成已经比较傻瓜式了。

开发工具

使用官方提供的 HBuilder

配置调整

  • 菜单栏 HBuilderX -> 偏好设置 -> 源码视图,修改以下配置:
    {
        "editor.fontSize": 12, // 初始字体太大了
        "editor.insertSpaces": true // 使用空格代替tab缩进
    }
  • 自动格式化,注意需要自行配置 eslint-vue,增加 rule,默认只使用了 base
  • 安装 scss 和内置浏览器插件

Mock

借助 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

可绕过

  • pages.json 在条件编译时直接启动开发运行模式报错:先注释掉 hint 提示报错的部分,待开发 server 启动后删除注释进行差量编译
  • 不支持在组件 html 中直接写变量修改:事件绑定响应方法,方法内处理赋值操作

待解决

  • 代码格式化与自动格式化不是同一套 formatter
  • pages.json 使用条件编译时 hint 提示语法错误
  • 移动端样式长度单位,小程序是 rpx,转成 H5 应该是 vh 或 vw,但是貌似不支持
  • 微信小程序组件的正确写法

可参考博客

Taro【React】

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';

Mock

与 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> 创建页面
  • tlife 的引入:直接在 jsx 中写 import { tlife } from '../../static/tlife-jssdk-2.0.0.min.js' 即可

已解决

  • chrome 下 react developer tools 报错: 是 devtools 的版本问题,Taro 内部固定了 React Native 的版本为 59.9,所以最新的 devTools v4 版本无法支持,需要使用 v3 版本,可以从react-deltools下载 v3.4.2 版本,或者参照 如何获取旧版本的开发者工具 自行操作获取 v3.6.3 版本
  • 设计稿尺寸问题,官方文档又写反了
    • config/index.js
      {
          "designWidth": 375,
          "deviceRatio": {
              "375": 2 / 1,
              "640": 2.34 / 2,
              "750": 1,
              "828": 1.81 / 2
          }
      }
  • 跨平台处理

待解决

有关 CSS 命名

CSS模块

A CSS Module is a CSS file in which all class names and animation names are scoped locally by default.

组件的css样式和html结构必定是息息相关,合理的css命名能方便理解html的结构也方便维护和复用。

BEM

Block Element Modifier

.block {}
.block__element {}
.block--modifier {}
block-name__element-name--modifier-name
  • Block: 一个完整的有意义的实体
    header,contaienr,menu,checkbox,input,通常使用一个短名词来命名。
  • Element: block的一个子部分,它依附于block,和其他element共同组成block
    menu item,list item,checkboc caption,header title
  • Modifier: block或者element的一个标志(flag),用于改变他们的表现效果
    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 {}

COMCEPT

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.

WHY

  • 可复用,阅读css时很容易分清结构上的依赖,便于协作--
    很多时候不敢修改css是因为担心某处修改可能引发不期的副作用,而严格执行BEM则能回避掉这种问题。虽然情况很多不能一概而论,但是BEM确实是更好地书写和维护css代码的一种标准。
  • 结构和表现都在class命名中体现,不书写嵌套的规则--使css尽可能的扁平和低权重

实际运用时

.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>
  • 永远不要在不相关的块中覆盖样式
  • 当元素个体能正确显示时,避免书写不必要的父级元素

使用Sass

不生成嵌套层级的写法

.block {
	@at-root #{&}__element {}
	@at-root #{&}--modifier {}
}

其他

除了BEM,还有其他的CSS命名规则,如SMACSS、OOCSS,并不是说BEM就一定优于它们,而是它们包括BEM都意图于形成一套帮助理解css和保持其可维护的规则。

SUIT

起源于BEM,使用驼峰命名和连字符区分后代元素和修饰符

.ComponentName {}
.ComponentName--modifierName {}
.ComponentName-descendantName {}
.ComponentName.is-someState {}
.ProductDetails {}
.ProductDetails-price {}
.ProductDetails-title--sale {}

OOCSS

完整名称: Object Oriented CSS,“面向对象CSS”
是比较早期的一种组织、编写、管理css的方案

两个主要的原则:

  • 结构和皮肤分开(Separate structure and skin)
  • 内容和容器分开(Separate container and content)
.mod {}
.inner {}
.talk {}
.talk .inner {}

SMACSS

完整名称:Scalable and Modular Architecture for CSS,“可扩展、模块化CSS架构”
样式被划分为5个部分:

  • Base: 页面元素的样式,如body input button ul ol 等
  • Layout: 全局布局元素的样式
  • Module: 可复用的模块样式
  • State: 模块不同状态下和默认状态的样式
  • Theme: 不同的设计样式

在Layout、State、Module这三种中使用不同的前缀来作区分

  • layout-grid-
  • is-
  • module-

模块相关的元素使用一个 base name 作为前缀,如.exm.exm-caption

为模块编写样式时,避免使用id选择符和元素选择符

语义化命名

.example {}
.example-title {}
.example-contents {}

.example .is-important {} // 使用单独的状态
.example-important {} // 或者把模块和状态合并起来

Atomic CSS

.mt-1 { margin-top: 1px; }
.w-200 { width: 200px; }

AMCSS

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>

FUN

Flat hierarchy of selecters, Utility styles, Name-spaced components
层级扁平的选择器,通用样式,带有命名空间的组件

  • 推荐使用class去选择元素,避免不需要的层级
  • 鼓励使用原子式的样式处理常见的样式,如w100-width: 100%
  • 为每个组件使用一个简单的命名空间,如.sc--shop cart

一些其他厂规

NEC(网易)
ALICEUI(支付宝)

一些其他组件库的样式命名

bootstrap(block-name-element-name-modifier-name)
antd

命名方式和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 {}
}
element(BEM)
Bulma(原子式)

参考

Some black holes

啊,只是一些突然的小想法,不足成篇,先放在这里吧:relaxed:

nextTick原理

前置

  1. Vue的DOM更新是异步的

    可能你还没有注意到,Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会一次推入到队列中。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MutationObserver,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。

  2. Macrotask 与 Microtask 核心概念

源码学习

nextTick实际指向的是一个立即执行函数返回的结果,该结果是一个可执行的方法。

该立即执行函数内部持有几个变量:callbacks, pending, timerFunc, nextTickHandler

image

在这个IIFE执行过程中,timerFunc将被视情况处理为不同类型的函数

源码注释:虽然MutationObserver有更广泛的支持,但是在ios9.3.3及以上版本中会有严重问题,所以降级顺序为:

  • Promise.resolve().then()
  • MutationObserver.observe()
  • setTimeout(fn, 0)

image

在结束一系列初始化和timerFunc的设置后,返回实际的nextTick指向的方法如下

image

  • 方法在实际执行时向callbacks中推入回调函数
  • 同时设置pending为true,并调用timerFunc为Microtask队列添加nextTickHandler,这样做使同一Macrotask中的回调函数均被收集到callbacks数组中,在下一个Macrotask执行前被统一执行
  • nextTickHandler中,false掉pending,将callbacks置回空数组,回调按照推入顺序逐一被执行

说人话就是:收集当前任务队列中所有的nextTick callbacks,统一放到当前任务队列结束下一任务队列开始之前执行。

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

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

Recommend Topics

  • javascript

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

  • web

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

  • server

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

  • Machine learning

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

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.