___L_e_e

___L_e_e 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

在路上,走走,看看,停停.

个人动态

___L_e_e 提出了问题 · 2018-09-20

在 webstorm 中 用flow.js 声明类型后的 提示异常

图片描述
图片描述

为什么第一种写法webstorm 没有提示呢

关注 1 回答 0

___L_e_e 回答了问题 · 2017-12-11

函数中的return foods有什么意义,希望解释的清楚一点

这段代码就是返回有 count 的 goods 数组
相当于过滤了一遍
这代码太不简洁了,两年前的 js 思维

return this.goods.info.filter(_=>_.count)

关注 9 回答 10

___L_e_e 回答了问题 · 2017-10-09

解决请问如何用 python 优雅的 解析 url

f = furl('https://www.baidu.com?p=1&keywords=&category=7&state=1')

old_p = int(f.args['p'])

new_p = old_p + 1

f.args['p'] = new_p

print f.url

关注 2 回答 2

___L_e_e 提出了问题 · 2017-10-09

解决请问如何用 python 优雅的 解析 url

我想通过 urlparse 解析完 url 之后更改 query 的值,然后生成新的 url
但是我这么写的话,, 会报错,, 不知道有没有优雅又可行的办法

from urlparse import urlparse,urlunparse,parse_qs,parse_qsl

url_base = 'https://www.baidu.com?p=1&keywords=&category=7&state=1'

url_obj = urlparse(url_base)

query_obj = parse_qs(url_obj.query)

query_obj['p'] = ['2']

new_url = urlunparse(url_obj,query_obj)

关注 2 回答 2

___L_e_e 赞了文章 · 2017-09-11

前端路由实现与 react-router 源码分析

原文地址:https://github.com/joeyguo/blog/issues/2

在单页应用上,前端路由并不陌生。很多前端框架也会有独立开发或推荐配套使用的路由系统。那么,当我们在谈前端路由的时候,还可以谈些什么?本文将简要分析并实现一个的前端路由,并对 react-router 进行分析。

一个极简前端路由实现

说一下前端路由实现的简要原理,以 hash 形式(也可以使用 History API 来处理)为例,当 url 的 hash 发生变化时,触发 hashchange 注册的回调,回调中去进行不同的操作,进行不同的内容的展示。直接看代码或许更直观。

function Router() {
    this.routes = {};
    this.currentUrl = '';
}
Router.prototype.route = function(path, callback) {
    this.routes[path] = callback || function(){};
};
Router.prototype.refresh = function() {
    this.currentUrl = location.hash.slice(1) || '/';
    this.routes[this.currentUrl]();
};
Router.prototype.init = function() {
    window.addEventListener('load', this.refresh.bind(this), false);
    window.addEventListener('hashchange', this.refresh.bind(this), false);
}
window.Router = new Router();
window.Router.init();

上面路由系统 Router 对象实现,主要提供三个方法

  • init 监听浏览器 url hash 更新事件

  • route 存储路由更新时的回调到回调数组routes中,回调函数将负责对页面的更新

  • refresh 执行当前url对应的回调函数,更新页面

Router 调用方式以及呈现效果如下:点击触发 url 的 hash 改变,并对应地更新内容(这里为 body 背景色)

<ul> 
    <li><a href="#/">turn white</a></li> 
    <li><a href="#/blue">turn blue</a></li> 
    <li><a href="#/green">turn green</a></li> 
</ul> 
var content = document.querySelector('body');
// change Page anything
function changeBgColor(color) {
    content.style.backgroundColor = color;
}
Router.route('/', function() {
    changeBgColor('white');
});
Router.route('/blue', function() {
    changeBgColor('blue');
});
Router.route('/green', function() {
    changeBgColor('green');
});

20160513_150041

以上为一个前端路由的简单实现,点击查看完整代码,虽然简单,但实际上很多路由系统的根基都立于此,其他路由系统主要是对自身使用的框架机制的进行配套及优化,如与 react 配套的 react-router。

react-router 分析

react-router 与 history 结合形式

react-router 是基于 history 模块提供的 api 进行开发的,结合的形式本文记为 包装方式。所以在开始对其分析之前,先举一个简单的例子来说明如何进行对象的包装。

// 原对象
var historyModule = {
    listener: [],
    listen: function (listener) {
        this.listener.push(listener);
        console.log('historyModule listen..')
    },
    updateLocation: function(){
        this.listener.forEach(function(listener){
            listener('new localtion');
        })
    }
}
// Router 将使用 historyModule 对象,并对其包装
var Router = {
    source: {},
    init: function(source){
        this.source = source;
    },
    // 对 historyModule的listen进行了一层包装
    listen: function(listener) {
        return this.source.listen(function(location){
            console.log('Router listen tirgger.');
            listener(location);
        })
    }
}
// 将 historyModule 注入进 Router 中
Router.init(historyModule);
// Router 注册监听
Router.listen(function(location){
    console.log(location + '-> Router setState.');
})
// historyModule 触发回调
historyModule.updateLocation();

返回:
22

可看到 historyModule 中含有机制:historyModule.updateLocation() -> listener( ),Router 通过对其进行包装开发,针对 historyModule 的机制对 Router 也起到了作用,即historyModule.updateLocation() 将触发 Router.listen 中的回调函数 。点击查看完整代码
这种包装形式能够充分利用原对象(historyModule )的内部机制,减少开发成本,也更好的分离包装函数(Router)的逻辑,减少对原对象的影响。

react-router 使用方式

react-router 以 react component 的组件方式提供 API, 包含 Router,Route,Redirect,Link 等等,这样能够充分利用 react component 提供的生命周期特性,同时也让定义路由跟写 react component 达到统一,如下

render((
  <Router history={browserHistory}>
    <Route path="/" component={App}>
      <Route path="about" component={About}/>
      <Route path="users" component={Users}>
        <Route path="/user/:userId" component={User}/>
      </Route>
      <Route path="*" component={NoMatch}/>
    </Route>
  </Router>
), document.body)

就这样,声明了一份含有 path to component 的各个映射的路由表。

react-router 还提供的 Link 组件(如下),作为提供更新 url 的途径,触发 Link 后最终将通过如上面定义的路由表进行匹配,并拿到对应的 component 及 state 进行 render 渲染页面。

<Link to={`/user/89757`}>'joey'</Link>

这里不细讲 react-router 的使用,详情可见:https://github.com/reactjs/react-router

从点击 Link 到 render 对应 component ,路由中发生了什么

为何能够触发 render component ?

主要是因为触发了 react setState 的方法从而能够触发 render component。
从顶层组件 Router 出发(下面代码从 react-router/Router 中摘取),可看到 Router 在 react component 生命周期之组件被挂载前 componentWillMount 中使用 this.history.listen 去注册了 url 更新的回调函数。回调函数将在 url 更新时触发,回调中的 setState 起到 render 了新的 component 的作用。

Router.prototype.componentWillMount = function componentWillMount() {
    // .. 省略其他
    var createHistory = this.props.history;
    
    this.history = _useRoutes2['default'](createHistory)({
      routes: _RouteUtils.createRoutes(routes || children),
      parseQueryString: parseQueryString,
      stringifyQuery: stringifyQuery
    });
    
    this._unlisten = this.history.listen(function (error, state) {
        _this.setState(state, _this.props.onUpdate);
    });
  };

上面的 _useRoutes2 对 history 操作便是对其做一层包装,所以调用的 this.history 实际为包装以后的对象,该对象含有 _useRoutes2 中的 listen 方法,如下

function listen(listener) {
      return history.listen(function (location) {
          // .. 省略其他
          match(location, function (error, redirectLocation, nextState) {
            listener(null, nextState);
          });
      });
}

可看到,上面代码中,主要分为两部分

  1. 使用了 history 模块的 listen 注册了一个含有 setState 的回调函数(这样就能使用 history 模块中的机制)

  2. 回调中的 match 方法为 react-router 所特有,match 函数根据当前 location 以及前面写的 Route 路由表匹配出对应的路由子集得到新的路由状态值 state,具体实现可见 react-router/matchRoutes ,再根据 state 得到对应的 component ,最终执行了 match 中的回调 listener(null, nextState) ,即执行了 Router 中的监听回调(setState),从而更新了展示。

以上,为起始注册的监听,及回调的作用。

如何触发监听的回调函数的执行?

这里还得从如何更新 url 说起。一般来说,url 更新主要有两种方式:简单的 hash 更新或使用 history api 进行地址更新。在 react-router 中,其提供了 Link 组件,该组件能在 render 中使用,最终会表现为 a 标签,并将 Link 中的各个参数组合放它的 href 属性中。可以从 react-router/ Link 中看到,对该组件的点击事件进行了阻止了浏览器的默认跳转行为,而改用 history 模块的 pushState 方法去触发 url 更新。

Link.prototype.render = function render() {
    // .. 省略其他
    props.onClick = function (e) {
      return _this.handleClick(e);
    };
    if (history) {
     // .. 省略其他
      props.href = history.createHref(to, query);
    }
    return _react2['default'].createElement('a', props);
};
  
Link.prototype.handleClick = function handleClick(event) {
    // .. 省略其他
    event.preventDefault();
    this.context.history.pushState(this.props.state, this.props.to, this.props.query);
};

对 history 模块的 pushState 方法对 url 的更新形式,同样分为两种,分别在 history/createBrowserHistory 及 history/createHashHistory 各自的 finishTransition 中,如 history/createBrowserHistory 中使用的是 window.history.replaceState(historyState, null, path); 而 history/createHashHistory 则使用 window.location.hash = url,调用哪个是根据我们一开始创建 history 的方式。

更新 url 的显示是一部分,另一部分是根据 url 去更新展示,也就是触发前面的监听。这是在前面 finishTransition 更新 url 之后实现的,调用的是 history/createHistory 中的 updateLocation 方法,changeListeners 中为 history/createHistory 中的 listen 中所添加的,如下

function updateLocation(newLocation) {
   // 示意代码
    location = newLocation;
    changeListeners.forEach(function (listener) {
      listener(location);
    });
}
function listen(listener) {
     // 示意代码
    changeListeners.push(listener);
}

总结

可以将以上 react-router 的整个包装闭环总结为

  1. 回调函数:含有能够更新 react UI 的 react setState 方法。

  2. 注册回调:在 Router componentWillMount 中使用 history.listen 注册的回调函数,最终放在 history 模块的 回调函数数组 changeListeners 中。

  3. 触发回调:Link 点击触发 history 中回调函数数组 changeListeners 的执行,从而触发原来 listen 中的 setState 方法,更新了页面

至于前进与后退的实现,是通过监听 popstate 以及 hashchange 的事件,当前进或后退 url 更新时,触发这两个事件的回调函数,回调的执行方式 Link 大致相同,最终同样更新了 UI ,这里就不再说明。

react-router 主要是利用底层 history 模块的机制,通过结合 react 的架构机制做一层包装,实际自身的内容并不多,但其包装的思想笔者认为很值得学习,有兴趣的建议阅读下源码,相信会有其他收获。

查看原文

赞 10 收藏 80 评论 2

___L_e_e 回答了问题 · 2017-08-07

解决boostrap默认的头部下拉菜单是如何绑定的事件?

https://github.com/twbs/boots...
源码在这里,
还有 我想说, 事件绑定不止一种办法, 题主应该查一下

关注 3 回答 2

___L_e_e 回答了问题 · 2017-08-07

webpack 打包遇到问题

升级到 node 8.0
然后删除 node_modules 再 npm install
在 node 8.0 中 npm 5 可以解决你的问题

关注 3 回答 2

___L_e_e 回答了问题 · 2017-08-07

解决如何模拟用户输入去测试一个在线上的网站.

headless chrome 或者其他无界面的 webDriver nightmare phantomjs 等等

关注 6 回答 6

___L_e_e 赞了文章 · 2017-08-07

如何优雅高效地插入百度广告

什么是百度广告

最近跟百度广告打了会交道,如果您正在或者即将和百度广告打交道,那太好了,本文一定会让您不虚此行。百度广告,即百度联盟广告,在 这里 进行注册后,经过 一些配置,便可以生成一段 js,将该 js 插入到 HTML 页面中,便能出现百度的广告。

恩,百度广告您一定见过,随便截张图:

随便给一段生成的百度广告代码感受下:

<script type="text/javascript">
   var cpro_id = "u2557752";
</script>
<script data-original="http://cpro.baidustatic.com/cpro/ui/cm.js" type="text/javascript"></script>

以上代码如何能够展现广告?最重要的原因是 cm.js 中将广告内容用 document.write 方法输出。

一般场景

一般场景,也是最简单的使用,需要广告出现在哪个位置,就把该段 js 放在哪个位置。非常容易理解,因为广告的生成用的是 document.write,所以执行到该段 js 时,会同步输出广告内容。

使用方式大概这样:

<html>
<body>
<!-- DOM 元素 -->
<script type="text/javascript">
   var cpro_id = "u2557752";
</script>
<script data-original="http://cpro.baidustatic.com/cpro/ui/cm.js" type="text/javascript"></script>
<!-- DOM 元素 -->
<script type="text/javascript">
    var cpro_id = "u2557760";
</script>
<script data-original="http://cpro.baidustatic.com/cpro/ui/cm.js" type="text/javascript"></script>
<!-- DOM 元素 -->
</body>
</html>

异步加载

以上的一般场景下,如果有性能瓶颈,很显然是因为百度广告图片加载的问题,js 的加载并不是首要原因(js还有缓存)。出于好奇,还是首先对 cm.js 试试能否异步加载。

<body>
<script>
  function scriptDomElement(u) {
    var s = document.createElement('script')
      , h = document.getElementsByTagName('head')[0];

    s.src = u;
    s.async = true;

    h && h.insertBefore(s, h.firstChild);
  }

  var cpro_id = "u2557760";
  
  scriptDomElement("http://cpro.baidustatic.com/cpro/ui/cm.js");
</script>
</body>

控制台有个 warning,但是广告也出来了:

之前我们说到百度广告是用 document.write 同步输出到页面上的,很显然,并不能异步加载有 document.write 方法的 js 文件(到底是重写页面呢,还是重写页面呢),所以会有该 warning。但是,为什么会有广告呢?

这是因为百度有一套 "备胎" 方案。当 cm.js 内部判断该 js 是被异步加载的时候,随即执行这套备胎方案:

createBackupWrapper: function(t) {
    try {
        var e = document.getElementsByTagName("script")
          , i = e[e.length - 1];
        if (i) {
            var n = i.parentNode;
            if (n) {
                var o = document.createElement("div");
                return o.id = t.containerId,
                n.insertBefore(o, i),
                !0
            }
        }
    } catch (r) {}
    return !1
    },

代码写的很清楚,就是把广告元素插入到最后一个 script 标签的前面。为了保证广告所在的位置即是我们希望的位置,很显然最后一个 script 元素必须就是 cm.js

这样理解的话,我们似乎可以得出这样一个结论:当广告位置在页面最底部时(并且只有一处广告位),我们可以对这段广告的 js 进行如上的异步加载。但是 js 异步加载了,广告所需要的图片还得请求,标签页上的小圈圈还是一直在转,所以我觉得对 cm.js 文件进行异步加载,并没有什么卵用。

事实上,cm.js 内部就提供了异步加载的方案 -> 广告位异步加载代码

<div id="PAGE_AD_1"></div>
HELLO WORLD
<div id="PAGE_AD_2"></div>

<!--广告位代码放在页面最后-->
<script type="text/javascript" data-original="http://cbjs.baidu.com/js/m.js"></script>

<!--异步加载开始-->
<script type="text/javascript">

BAIDU_CLB_fillSlotAsync('u2557752','PAGE_AD_1');//12345是广告编号,PAGE_AD_1是您要投放广告的位置

BAIDU_CLB_fillSlotAsync('u2557760','PAGE_AD_2');

</script>
<!--异步加载结束 -->

这样不仅 cm.js 只需加载一次,而且调用也方便多了。(好吧,之前的异步测试算是无用功)

延迟加载

单纯的异步加载对于页面整体的加载速度似乎并没有什么提升(广告图片略多),是否可以用 setTimeout 进行延迟的异步加载?ok,我们对之前的代码进行一点改造,用一个定时器延迟执行 scriptDomElement 函数。

<body>
<script>
  function scriptDomElement(u) {
    var s = document.createElement('script')
      , h = document.getElementsByTagName('head')[0];

    s.src = u;
    s.async = true;

    h && h.insertBefore(s, h.firstChild);
  }

  var cpro_id = "u2557760";
  
  setTimeout(function() {
      scriptDomElement("http://cpro.baidustatic.com/cpro/ui/cm.js");
  }, 2000);
</script>
</body>

前面说了,异步加载仅适用于 cm.js 作为最后一个 script 标签的情况,也就是广告在页面最底部的情况。what's more,经过这样的处理,大多数情况下是可以看到广告的,但是小部分情况广告无法展现,究其原因,楼主觉得是 cm.js 内部对该 js 是否是异步加载无法精确判断。

所以楼主觉得,如果可以接受某些机器下无法展现百度广告,这个方法还是可以一试的。

恩,其实我们完全可以用 cm.js 提供的 BAIDU_CLB_fillSlotAsync 方法和 setTimeout 进行配合。

重写 document.write

强势插入硬广一枚:楼主 Github 求关注~

非常简单,写个简单的 DEMO(重写完后记得改回来,DEMO 中没改回来):

<body>
Hello world!
<div id='ad'>
</div>
<script>
  // 重写 document.write
  document.write = function( text ){
    document.getElementById('ad').innerHTML = text;
  };

  function scriptDomElement(u) {
    var s = document.createElement('script')
      , h = document.getElementsByTagName('head')[0];

    s.src = u;
    s.async = true;

    h && h.insertBefore(s, h.firstChild);
  }

  var cpro_id = "u2557760";
  scriptDomElement("http://cpro.baidustatic.com/cpro/ui/cm.js");
</script>
</body>

如何能做到延迟加载?我们可以采用 BigRender 的思路,将广告代码放在 textarea 标签中,当 textarea 出现在视野中时,取出广告代码执行。参考雨夜带刀的代码:

<div>
<textarea style="display:none">
<script type="text/javascript" data-original="http://gg.5173.com/adpolestar/5173/
;ap=2EBE5681_1BA3_4663_FA3F_E73D2B83FBDC;ct=js;pu=5173;/?"></script>
</textarea>
</div>
延迟加载script并重写document.write,下面是代码实现:

var loadScript = function( elem ){
    var url = elem.value.match( /data-original="([\s\S]*?)"/i )[1],
        parent = elem.parentNode,
        // 缓存原生的document.write
        docWrite = document.write,    
        // 创建一个新script来加载
        script = document.createElement( 'script' ), 
        head = document.head || 
            document.getElementsByTagName( 'head' )[0] || 
            document.documentElement;
    
    // 重写document.write
    document.write = function( text ){
        parent.innerHTML = text;
    };

    script.type = 'text/javascript';
    script.src = url;
    
    script.onerror = 
    script.onload = 
    script.onreadystatechange = function( e ){
        e = e || window.event;
        if( !script.readyState || 
        /loaded|complete/.test(script.readyState) ||
        e === 'error'
        ){

            // 恢复原生的document.write
            document.write = docWrite;
            head.removeChild( script );
            
            // 卸载事件和断开DOM的引用
            // 尽量避免内存泄漏
            head =             
            parent = 
            elem =
            script = 
            script.onerror = 
            script.onload = 
            script.onreadystatechange = null;

        }
    }
    
    // 加载script
    head.insertBefore( script, head.firstChild );
};

如果有多个广告片段,因为 document.write 是全局方法,所以不得不维护个脚本队列,一个一个执行,又退化成了同步执行脚本。如果异步并发执行的话,很可能广告的位置会出现对调现象。当然,有些百度广告并不会十分在乎顺序,比如下面要说的新闻信息流。

其实我觉得如果要延迟加载某些特定位置的广告区域,可以用 BAIDU_CLB_fillSlotAsync 方法,将该方法所在的代码塞入 textarea 中。 有一点需要注意的是,BAIDU_CLB_fillSlotAsync 必须指定广告位置的 DOM id。

插入到新闻信息流

将百度广告插入到新闻信息流,这是很普遍的做法。

比如网易:

那么如何将广告插入到新闻信息流当中去呢?我们还是可以用重写 document.write 的方法。

举个简单的例子:

<body>
<ul>
  <li class="news"></li>
  <li class="news"></li>
  <li class="news"></li>
  <li class="bdad"></li>
  <li class="news"></li>
</ul>
<script>
  // 重写 document.write
  document.write = function( text ){
    document.getElementsByClassName('bdad')[0].innerHTML = text;
  };
  
  function scriptDomElement(u) {
    var s = document.createElement('script')
      , h = document.getElementsByTagName('head')[0];

    s.src = u;
    s.async = true;

    h && h.insertBefore(s, h.firstChild);
  }

  var cpro_id = "u2557760";
  scriptDomElement("http://cpro.baidustatic.com/cpro/ui/cm.js");
</script>
</body>

重写一个系统的方法毕竟不是什么好事,网易头条新闻 采用的都是另一种方法,套一个 iframe,非常巧妙。

index.htm 文件:

<body>
<ul>
  <li class="news"></li>
  <li class="news"></li>
  <li class="news"></li>
  <li class="news"><iframe data-original="ad.htm"></iframe></li>
  <li class="news"></li>
</ul>
</body>

ad.htm 文件:

<script type="text/javascript">
  var cpro_id = "u2557760";
</script>
<script data-original="http://cpro.baidustatic.com/cpro/ui/cm.js"></script>

当然,一些样式方面的细节还需要自己去把握,这里只提供一个思路。

总结

对于百度广告在不同环境中的投放,有不同的处理方式。主要有三种:

  • 利用 cm.js 中的 BAIDU_CLB_fillSlotAsync 方法(该方法需要广告位置的 DOM id)

  • 重写 document.write

  • 新建 iframe,在该 iframe 中同步输出广告代码

仁者见仁智者见智吧。

Read more:

查看原文

赞 6 收藏 17 评论 10

___L_e_e 关注了专栏 · 2017-02-17

勤勉的JS与React

个人心得,偏JavaScript, React, React Native

关注 37

认证与成就

  • 获得 1 次点赞
  • 获得 5 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-10-31
个人主页被 352 人浏览