闭包问题

闭包问题详解

一、什么是闭包?
“官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
相信很少有人能直接看懂这句话,因为他描述的太学术。我想用如何在Javascript中创建一个闭包来告诉你什么是闭包,因为跳过闭包的创建过程直接理解闭包的定义是非常困难的。看下面这段代码: 
function a(){
var i=0;
function b(){
alert(++i);
}
return b;
}
var c = a();
c();
这段代码有两个特点:
1、函数b嵌套在函数a内部;
2、函数a返回函数b。
这样在执行完var c=a()后,变量c实际上是指向了函数b,再执行c()后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,为什么?因为函数a外的变量c引用了函数a内的函数b,就是说:

当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。

我猜想你一定还是不理解闭包,因为你不知道闭包有什么作用,下面让我们继续探索。

二、闭包有什么作用?
简而言之,闭包的作用就是在a执行完并返回后,闭包使得Javascript的垃圾回收机制GC不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量。这是对闭包作用的非常直白的描述,不专业也不严谨,但大概意思就是这样,理解闭包需要循序渐进的过程。
在上面的例子中,由于闭包的存在使得函数a返回后,a中的i始终存在,这样每次执行c(),i都是自加1后alert出i的值。

那 么我们来想象另一种情况,如果a返回的不是函数b,情况就完全不同了。因为a执行完后,b没有被返回给a的外界,只是被a所引用,而此时a也只会被b引 用,因此函数a和b互相引用但又不被外界打扰(被外界引用),函数a和b就会被GC回收。(关于Javascript的垃圾回收机制将在后面详细介绍)

三、闭包内的微观世界
如 果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境(excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。

1、当定义函数a的时候,js解释器会将函数a的作用域链(scope chain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。
2、当函数a执行的时候,a会进入相应的执行环境(excution context)。
3、在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。
4、然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。
5、下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。
6、最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。

到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。

当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、a的活动对象和window对象,如下图所示:

如图所示,当在函数b中访问一个变量的时候,搜索顺序是先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依 次查找,直到找到为止。如果整个作用域链上都无法找到,则返回undefined。如果函数b存在prototype原型对象,则在查找完自身的活动对象 后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。

四、闭包的应用场景
1、保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。
2、在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。
以上两点是闭包最基本的应用场景,很多经典案例都源于此。

五、Javascript的垃圾回收机制
在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。

组件API

posted @ 2015-09-12 21:45 vajoy 阅读(134) 评论(0) 编辑 收藏
 

本篇将介绍 React 组件的API,其中主要的几个API我们在第一篇的时候便已介绍过,这里可以做个温故知新。

本篇的代码你也可以在我的Github上获取到。

setState

参数: nextState(object), [callback(function)]

设置 nextState 的某个键(属性)(别忘了我们可以在 shouldComponentUpdate 方法中获取到 nextState 的值来跟当前的 this.state 进行对比),然后下一次 EventLoop 时 this.state 的对应键就会被更新为该设定的值。

通常我们如果希望在某个事件或某个回调中来重新渲染组件(触发UI更新内容),setState 是一个最常用的触发方法,因为我们把UI内容跟state状态直接(或者通过某些条件)绑定在了一起,一旦state发生改变并触动了绑定的逻辑,那么UI内容自然也会跟着变动:

复制代码
    var Component1 = React.createClass({
        getInitialState: function() {
            return {
                isClick: !1
            }
        },
        componentDidUpdate: function(){
            console.log('componentDidUpdate')
        },
        clickCb: function() {
            this.setState({
                isClick : !0
            }, function(){
                console.log(this.state.isClick)
            })
        },
        render: function() {
            return (<div onClick={this.clickCb}>
            isClick:{this.state.isClick ? 'yes' : 'nope'}
            </div>)
        }
    });
    var div = document.getElementById('a');
    React.render(
        <Component1 />, div
    );
复制代码

如上我们通过 state.isClick 来决定div内要显示的内容,而我们点击div时会改变 state.isClick 的状态,从而触发绑定条件更改了div中的内容。

注意这段代码里我们使用了 componentDidUpdate 方法,如第二篇文章中所介绍的,它在组件重新渲染后会立即触发。我们点击 div 之后发现打印顺序是这样的:

即我们在 setState 方法中所定义的回调,它是在组件重新渲染之后才执行的,而不是在我们变更了 nextState 的值之后就立即触发。

replaceState

参数: nextState(object), [callback(function)]

类似于 setState(),但是删除之前所有已存在的 state 键(key),这些键都不在 nextState 中。

这句话怎么理解呢?我们看这个例子:

复制代码
    var Component1 = React.createClass({
        getInitialState: function() {
            return {
                isClick: !1,
                abc: 1   //注意这里我们初始化了一个 state.abc
            }
        },
        clickCb: function() {
            this.replaceState({
                isClick : !0
            }, function(){
                console.log(this.state)
            })
        },
        render: function() {
            return (<div onClick={this.clickCb}>
            isClick:{this.state.isClick ? 'yes' : 'nope'}
            </div>)
        }
    });
    var div = document.getElementById('a');
    React.render(
        <Component1 />, div
    );
复制代码

注意我们在 getInitialState 里还额外定义了一个 abc 的 state 属性,但在 replaceState 后再试着打印 this.state,会发现这个 state.abc 已经被删除了(如果换成 setState 则不会被删除)

所以顾名思义,replaceState 就是一个彻底更换掉 state 的方法,寻常使用的时候需要小心使用,避免删掉一些重要的state属性。

forceUpdate

参数: [callback(function)]

顾名思义就是在任何调用的时刻强制渲染组件,例如即使 shouldComponentUpdate 返回了false:

复制代码
    var Component1 = React.createClass({
        shouldComponentUpdate: function(nextProps, nextState){
            console.log( this.state, nextState );
            this.forceUpdate(function(){
                console.log('强制渲染完成')
            }); //强制渲染,去掉这行则不会渲染
            return false;
        },
        componentDidUpdate: function(){
            console.log('componentDidUpdate')
        },
        getInitialState: function() {
            return {
                isClick: !1
            }
        },
        clickCb: function() {
            this.setState({
                isClick : !this.state.isClick
            })
        },
        render: function() {
            return (<div onClick={this.clickCb}>
            isClick:{this.state.isClick ? 'yes' : 'nope'}
            </div>)
        }
    });
    var div = document.getElementById('a');
    var c1 = React.render(
        <Component1 />, div
    );
复制代码

注意该方法的回调,也是在重新渲染之后才执行的。

另一个使用 forceUpdate 的场景可以是,我们不以 props 或 state 来作为触发渲染的条件,例如使用了一个变量来作为UI内容,在该变量的值改变了且我们希望触发渲染时,可以使用该方法,当然这种形式是不推荐的。

getDOMNode()

返回组件/ReactElement挂载到页面上所对应的DOM元素:

复制代码
    var Component1 = React.createClass({
        getInitialState: function() {
            return {
                isClick: !1
            }
        },
        clickCb: function() {
            this.setState({
                isClick : !0
            }, function(){
                console.log(this.state.isClick)
            })
        },
        render: function() {
            return (<div onClick={this.clickCb}>
            isClick:{this.state.isClick ? 'yes' : 'nope'}
            </div>)
        }
    });
    var div = document.getElementById('a');
    var c = React.render(
        <Component1 />, div
    );
    console.log(c.getDOMNode())
复制代码

打印结果:

另外,若 render 返回 null 或者 false 的时候,this.getDOMNode() 将返回 null。

isMounted()

返回一个 Boolean 值,如果组件挂载到了 DOM 中,isMounted() 返回 true。

其适用于异步请求的情景下:

复制代码
    var Component1 = React.createClass({
        getInitialState: function() {
            return {
                content: 'hello'
            }
        },
        componentWillMount: function () {
            doSomething(props).then(function (content) {
                if (this.isMounted()) {
                    this.setState({content: content});
                }
            }.bind(this));
        },
        render: function() {
            if(this.state.isClick){
                return (<div>
                content:{this.state.content}
                </div>)
            } else {
                return false;
            }
        }
    });
    var div = document.getElementById('a');
    var c = React.render(
        <Component1 />, div
    );
复制代码

如上代码,我们在 componentWillMount 的时候执行一个异步请求 doSomething,在其获取到服务器数据的时候修改 state 的值,前提是组件已经挂载到页面上。

如果该异步请求完成得很快,我们获取到新content时候组件可能还在处于挂载中(mounting)的过程,那么 state 则保持不变(因为此时 isMounted() 将返回false )。

setProps

参数: nextProps(object), [callback(function)]

跟 setState 类似,不过修改的是 props 罢了:

复制代码
    var Component1 = React.createClass({
        clickCb: function() {
            this.setProps({
                name : 'cnBlog'
            }, function(){
                console.log(this.props)
            })
        },
        render: function() {
            return (<div onClick={this.clickCb}>
                    {this.props.sayhi || 'www.'}
                    {this.props.name || 'nothing'}
            </div>)
        }
    });
    var div = document.getElementById('a');
    React.render(
            <Component1 name="VaJoy" sayhi="Hello " />, div
    );
复制代码

replaceProps

参数: nextProps(object), [callback(function)]

类似于 setProps,会替换掉现有的 props 的键,我们依旧使用上一段代码,不过把 setProps 换成 replaceProps:

复制代码
    var Component1 = React.createClass({
        clickCb: function() {
            this.replaceProps({
                name : 'cnBlog'
            }, function(){
                console.log(this.props)
            })
        },
        render: function() {
            return (<div onClick={this.clickCb}>
                    {this.props.sayhi || 'www.'}
                    {this.props.name || 'nothing'}
            </div>)
        }
    });
    var div = document.getElementById('a');
    React.render(
            <Component1 name="VaJoy" sayhi="Hello " />, div
    );
复制代码

我们这时再点击组件,会发现 props.sayHi 已被删除了:

Refs

事实上这个不属于组件的方法,但也是在组件中常用的一个小技巧,故在这里做介绍。

我们在前面提到,可以使用 ReactClass.getDOMNode() 的方法来获取到组件渲染在页面上的DOM节点,但是如果我们希望获取到的,是组件中的某个DOM元素呢?

这时候我们可以使用定义 refs 的形式,来方便我们获取对应的元素:

复制代码
    var Component1 = React.createClass({
        clickCb: function(e) {
            if(e.target === this.refs.li2.getDOMNode()){
                alert('你点到第二个LI了')
            }
        },
        render: function() {
            return (<ul onClick={this.clickCb}>
                <li>1</li>
                <li ref="li2">2</li>
                <li>3</li>
            </ul>)
        }
    });
    var div = document.getElementById('a');
    React.render(
            <Component1 />, div
    );
复制代码

如上我们给组件中的第二个<li>绑定了一个 ref 属性,值为“li2”,这意味着我们可以在组件中以 this.refs.li2 的形式来获取到该ReactElement。

然后绑定点击事件,在点击的时候判断被点击的 li 元素是否等于 this.refs.li2.getDOMNode(),是的话则弹出alert。

官方也给出了一个实用的demo:

复制代码
  var App = React.createClass({
    getInitialState: function() {
      return {userInput: ''};
    },
    handleChange: function(e) {
      this.setState({userInput: e.target.value});
    },
    clearAndFocusInput: function() {
      // Clear the input
      this.setState({userInput: ''}, function() {
        // 组件重绘后会立即执行这句代码:
        this.refs.theInput.getDOMNode().focus();   // input成功聚焦(focus)
      });
    },
    render: function() {
      return (
        <div>
          <div onClick={this.clearAndFocusInput}>
            Click to Focus and Reset
          </div>
          <input
            ref="theInput" //我们可以在组件里以 this.refs.theInput 获取到它
            value={this.state.userInput}
            onChange={this.handleChange}
          />
        </div>
      );
    }
  });
复制代码

到此位置我们便基本入门了 React 的大部分主要的API,后续我们将以项目实践的形式来进一步学习React。

原文地址:https://www.cnblogs.com/Leo_wl/p/4803938.html