分类 Javascript 下的文章

关于 Redux 中间件

Redux 的中间件是定义一个函数,对 dispatch 进行改造,在发出 action 与执行 reducer 之间添加其他功能,这是对 Redux 进行功能拓展的方式。

Redux 如何支持中间件?

applyMiddlewares()

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer);
    var dispatch = store.dispatch;
    var chain = [];
    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };
    chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain)(store.dispatch);
    return {...store, dispatch}
  }
}

middleware => middleware(middlewareAPI)

每个 middleware 将能访问 getStatedispatch,同时可以知道该 middleware 为高阶函数,执行返回一个函数

compose()

dispatch = compose(...chain)(store.dispatch);

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

(composed, f) => f(composed)
即:
middleware({getState, dispatch})(store.dispatch)
该函数为高阶函数,执行返回一个函数

分析至此,一个 middleware 函数大致为:

({getState, dispatch})=> (next) => (action) => {
    // next 即 store.dispatch
    //...在此拓展功能
    action() // action, 这个action执行触发dispath??
 } 

Action 如何与Dispatch联系起来?

action 通过 bindActionCreatordispatch 联系起来

bindActionCreator()

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

(...args) => dispatch(actionCreator(...args))

actionCreator 可以知道 actionCreator 为高阶函数,执行返回一个函数

所以一个 action 的姿势应该为:

()=> () => {
   type: "",
   payload
}

举例:redux-thunk 中间件

源码:

function thunkMiddleware({ dispatch, getState }) {
  return next => action =>
    typeof action === 'function' ?
      action(dispatch, getState) :
      next(action);
}

action 怎么写?

funciton fetchSomething(){
    return (dispatch, getState)=>{
        fetch().then((res)=>{  
            disatch({
                type: "FETCH_SOMETHING_DONE"
                palyload
            })
        })
    }
}

参考:

Redux Middleware Doc

关于 FormData

介绍

先看看 MDN 对FormData的介绍:

XMLHttpRequest Level2添加了一个新的接口FormData. 利用FormData对象,我们可以通过JavaScript用一些键值对来模拟一系列表单控件,我们还可以使用XMLHttpRequest的send()方法来异步的提交这个"表单".比起普通的ajax,使用FormData的最大优点就是我们可以异步上传一个二进制文件.

特点就是,增加了ajax对二进制文件上传的支持。比如:

var formData = new FormData();
formData.append("username", "sam");
// HTML file input, chosen by user
formData.append("userfile", fileInputElement.files[0]);
// JavaScript file-like object
var content = '<a id="a"><b id="b">hey!</b></a>'; // the body of the new file...
var blob = new Blob([content], { type: "text/xml"});
formData.append("webmasterfile", blob);
var request = new XMLHttpRequest();
request.open("POST", url);
request.send(formData);

这个例子既有File,还有Blob 类型文件,利用FormData 能轻松的进行异步上传。

请求头部

request_header

可以看到 Content-Type 为:

multipart/form-data; boundary=----WebKitFormBoundary2KZkAN7R3gSDjBJz

图中没有看到 file 的二进制文件

请求主体

request_body_head
request_body_tail

每个参数包括:

  1. 第一行,“分界线”,图中chrome的分界线是:“——WebKitFormBoundary”加16位随机Base64位编码的字符串作为分隔边界
  2. 第二行,内容配置,指明form-data,和name键名称
  3. 第三行,指明content-type
  4. 第三行或第四行,空行

注意,整个body结束最后,多了一个分界线

分析一下请求信息,发现这种提交方式与form表单提交是一样的,比如:

<form enctype="multipart/form-data" method="post" action="">
    <input name="username" value="sam1">username</input>
    <button type="submit">提交</button>
</form>

兼容性

form_data_compatibility

由图可见,FormData 在移动端,对低版本的 Android 设备支持不够友好。

我们知道,在ajax请求中,send函数会自动生成请求头和请求主体。所以,send(formData)浏览器会自动生成上图中的请求。在浏览器不支持FormData的情况下,我们可以通过拼接multipart/form-data请求,来达到目的。

动手实现了一个:https://github.com/henryluki/FormData

参考

FormData
The Multipart Content-Type
XMLHttpRequest Standard
Returning Values from Forms: multipart/form-data

关于 debounce 和 throttle

去抖( debounce ): 去除抖动,只执行一次
节流( throttle ): 限制执行次数,定时执行

在( scroll事件 + resize事件 + touchmove事件 + 实时搜索)可能会使用其中一个函数,但是这两个函数有什么区别呢?

首先,来看一下两个函数实现:

function debounce(fn, ts){
    var debounce_timer

    return function(){
        if (debounce_timer) {
           clearTimeout(debounce_timer)
           debounce_timer = null
        }
        debounce_timer = setTimeout(fn, ts || 1000)
    }
}

function throttle(fn, wait){
    var context,
        args,
        result,
        previous = 0,
        throttle_timer = 0,
        remaining
        wait = wait || 1000

    var later = function(){
        previous = Date.now()
        result = fn.apply(context, args)
    }

    return function(){
        context = this
        args = arguments

        if (!previous) {
            previous = Date.now()
        }
        remaining = wait - (Date.now() - previous)

        if (remaining <= 0) {
            if (throttle_timer) {
                clearTimeout(throttle_timer)
                throttle_timer = 0
            }
            previous = Date.now()
            result = fn.apply(context, args)
            context = args = null
        } else if (!throttle_timer){
            // 最后一次执行
            throttle_timer = setTimeout(later, remaining)
        }
        return result
    }
}

代码实现不难,本质都是利用定时器( setTimeout ),但是看起来,throttle 比 debounce 复杂一些。

通过一个试验来看一下两个函数分别有哪些特点。

通过监听 scroll、resize、touchmove 事件,使用 debounce 和 throttle 处理回调(添加内容)来进行测试

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>debounce</title>
<style type="text/css">
body {
    font-size: 14px;
}

#main{
    height: 100vh;
    overflow: hidden;
}

.content {
    height: 100vh;
    overflow-y: auto;
}

</style>
</head>
<body>
<div id="main">
    <div class="content"></div>
</div>
</body>
<script type="text/javascript">
window.onload = addContent

var count = 0

var addContent = function(){
    var content = document.querySelector(".content")
    var html = []
    for (var i = 0; i < 15; i++) {
        var text = count + "离得近了肯德基分类的解放军的了解到了缴费的空间放开了的解放军大家都来缴费离得近"
        count += 1
        html.push("<p>" + text + "</p>")
    }

    var fragment = document.createElement('div')
    fragment.innerHTML = html.join(" ")
    content.appendChild(fragment)
}

var a = throttle(addContent, 1000) // 节流
var b = debounce("addContent", addContent, 1000) // 去抖

var addEvents = function(cb){
    ["scroll", "resize", "touchmove"].map(function(e){
        window.addEventListener(e, cb)
    })
}

addEvents(b)

</script>
</html>

经过测试,得到一下特点:

  • debounce 在触摸停止时,延迟一定时间执行一次回调函数
  • throttle 持续滚动时,定时执行回调函数,在触摸(滚动)停止时,执行了两次回调函数

体验上:

  • 使用 debounce 函数,添加内容有1秒的延迟
  • 使用 throttle 函数,添加内容没有延迟但是因为执行了两次,内容添加过多

总结:

个人觉得,在纠结是使用 debounce 还是 throttle 来处理重复触发的事件时,采用 debounce 函数来处理回调,通过调整延迟时间,体验比使用 throttle 函数体验更佳

<<精通javascript开发>> 简单笔记

性能

  • 事件冒泡 -> 父级元素处理子元素的事件

  • cpu密集 -> web worker OR 事件委托 事件委托 -> 定时执行处理函数

  • DOM变动 -> 使用父级元素替代多个子元素的操作

  • GZIP -> 传输时压缩,抵达浏览器解压缩

  • 避免js 对样式的直接改动 -> 使用css

  • 减少js 对DOM的append -> 最后才append

  • js代码需要时才加载- > requirejs...

  • 代码的混淆编译… -> webpack...

代码规范

  • 私有变量 -> 私有的,外部不可访问的变量和函数用 "_"

  • Getter 和 Setter -> 提供对变量的操作的接口

  • 继承 -> 提供create接口 和 options 参数对class 的初始化

  • es5的新特性 -> forEach Object.defineProperty ...

JavaScript内存优化

寒假系统学习javascript的时候,写了一些简单的练习代码,先来看下面一段:

//闭包
var global_ten=10;
var sum;

function papa() {
var hundred_more=100;
sum=function(a,b){
return a+b+global_ten+hundred_more;
};
hundred_more=10000;
}

papa();//changed the hundred_more
console.log(sum(9,11));//hundred_more has changed

//访问保留环境中存在的i
var fns=[];
function definer(){
for(var i=0;i<5;i++){
fns.push(function(){
return i;
});
}
}
definer();
for (var i = 0; i < fns.length; i++) {
console.log(fns[i]());
};
//引入另一个作用域对象local_i
//() 立即函数 立即执行
var fns_ano=[]
function definer_ano(){
for(var i=0;i<5;i++){
fns_ano.push(function(local_i){
return function(){
return local_i;
}
}(i));
}
}
definer_ano();
for (var i = 0; i < fns_ano.length; i++) {
console.log(fns_ano[i]());
};

闭包是JavaScript 的高级特性,我们可以借助它来实现更多更复杂的效果来满足不同的需求。但是要注意的是因为把带有​​内部变量引用的函数带出了函数外部,所以该作用域内的变量在函数执行完毕后的并不一定会被销毁,直到内部变量的引用被全部解除。所以闭包的应用很容易造成内存无法释放的情况。

还有以下几个概念:

作用域

域(scope)是JavaScript 编程中一个非常重要的运行机制,在同步JavaScript 编程中它并不能充分引起初学者的注意,但在异步编程中,良好的作用域控制技能成为了JavaScript 开发者的必备技能。另外,作用域在JavaScript 内存管理中起着至关重要的作用。

作用域链

由于JavaScript 中,变量标识符的查找是从当前作用域开始向外查找,直到全局作用域为止。所以JavaScript 代码中对变量的访问只能向外进行,而不能逆而行之。

以上应有图,但是搜了一下网上资源也很多,在此不赘述 推荐:JavaScript内存优化

ps:关于系统学习javascript语言之建议--《javascript for php developers》。注: 以上内容《深入浅出node.js》也有详解。