若是你还没忘的话,仔细回想一下,以前咱们是如何将对象绑定到变量名上的。但当时咱们只是全局绑定,在那时这种绑定是很是有用的。不过,有不少时候,本地绑定每每比全局绑定更合适,例如把变量限制在一个操做内部的时候。下面就让咱们看看若是使用绑定函数 "let " 进行本地绑定。 javascript
=>id java.lang.Exception: Unable to resolve symbol: id... =>(let [id 1] (println id)) 1 nil =>id java.lang.Exception: Unable to resolve symbol: id...正向你看到的南阳,经过使用"let" 操做,咱们把1绑定到了”id“这个变量名上。而后咱们又把它打印了出来。当这个操做执行完后,咱们在外面查看”id“时倒是没法解析的。这就证实了变量”id“只存在于操做内部(本地绑定相似于java中的方法局部变量)。
本地绑定能够将变量限制在某个操纵内,这样就不会形成对其余操做的变量污染。试想一下,若是没有本地绑定,一旦咱们使用了id这个变量名后,咱们就不再能使用它来绑定其余对象了。使用本地绑定后,咱们能够在某个操做内使用任何有意义的变量名而不用担忧和其余相同名字的变量形成冲突,即便是全局变量: java
=>(def id 0) ;;全局绑定 #'user/id =>id 0 =>(let [id 1] ;;本地绑定 (println id)) 1 nil =>id ;;本地绑定对全局绑定没有任何影响 0
这种行为一般被称为词法做用域,咱们能够保护变量不受污染,甚至是父操做也存在一样的变量名: 函数
=>(let [id 1] ;;外层操做 (let [id 2] ;;内层操做 (println id)) ;;内层本地绑定的id (println id)) ;;外层本地绑定的id 2 1 nil
咱们再来举一个例子。上一篇最后咱们写了一个”data-list"函数,这个函数最终返回给咱们一个包含各个时间元素的列表。咱们每调用一次,它都会返回给咱们当前时间年、月、日、时、分、秒组成的一个列表: .net
=>(date-list) ("2013" "07" "10" "15" "02" "59") =>(date-list) ("2013" "07" "10" "15" "03" "02")
如今呢,咱们想要这么一个函数 run-report,经过这个函数,咱们能只打印出"时"和"分"这两个元素。这个简单咱们能够向下面这样去实现它: code
=>(defn run-report [] (str "report ran: " (nth (date-list) 3) ":" (nth (date-list) 4))) #'user/run-report =>(run-report) "report ran: 15:04"
上面这个函数有什么问题吗?聪明的你就会发现data-list这个函数被调用了两次。一次咱们用来获取小时,一次咱们用来获取分钟。这样作的话有两个坏处。第一个是,这两次调用返回的时间是不同的(函数在快也须要时间执行),咱们极可能获得很是错误的结果。假如第一次调用刚好是15:59:59,到了接近16点的临界点。第二次调用变成了16:00:00。这两个组合就变成了 15:00 。第二个坏处就是,排除第一个错误的话,若是data-list执行时间比较长,屡次调用势必影响函数效率。 对象
更好的作法就是咱们只调用一次data-list,而后把调用后的结果绑定到一个本地变量上: blog
=>(defn run-report [ ] (let [date (date-list)] ;;data-list是全局变量。date是本地变量 (str "report ran " (nth date 3) ":" (nth date 4)))) #'user/run-report =>(run-report) "report ran: 15:09"经过上面的作法,以前的两个问题都不复存在了。
下面是另外一种作法: ip
=>(defn run-report [date] (str "report ran: " (nth date 3) ":" (nth date 4))) #'user/run-report =>(run-report (date-list)) ;; 传入参数就是一种隐式的本地绑定 "report ran: 15:10"咱们给run-raport 函数添加了一个参数date,参数对函数来讲就是一个隐式的本地绑定。当函数被执行时,用实参替换形参的时候,本地绑定就自动的和隐式的进行了。
关于词法做用域能够参考这篇文章,虽然是关于javascript的,但道理是同样的。 作用域