JS基礎知識(三)作用域和閉包
作用域和閉包
問題:
-
說一下對變數提升的理解
-
說明this幾種不同的使用場景
-
建立10個<a>,點選的時候彈出來對應的序號
-
如何理解作用域
-
實際開發中閉包的應用
知識點:
1.執行上下文(execution context)簡稱EC
概念:執行上下文,簡單的理解就是會提前做的一些“準備工作”。存在變數提升的現象。
變數:來看三個小例子,當你直接去列印一個不存在的變數,他自然會報錯,但當你在列印一個變數時,沒有提前宣告,在你需要列印的這句話下面再宣告,此時,就會顯示出準備工作的作用。會將他提前宣告,預設值為undefined
this:無論在哪裡去讀取this,都會有值,但是在不同的地方不同。這個比較複雜。
函式宣告:
此處有分別為兩點,一個是函式,另外一個是函式表示式。
函式宣告是會被提前的,但是函式表示式不會。
每一個script裡面的變數,函式都會被提前,同時還會一開始就確定this和arguments的值。
- 範圍:一段<script>或者一個函式
- 全域性:變數定義,函式宣告 一段<script>
- 函式:變數定義,函式宣告,this,arguments 函式執行之前生成
我們在日常的開發中,最好做到:先定義後執行
函式覆蓋:
函式宣告和變數宣告都會被提升,但是函式宣告會覆蓋變數宣告。
但是當變數已經被賦值的時候,最終的值就會為變數的值。
變數的重複宣告是無用的,但是函式的重複宣告會覆蓋前面的宣告
我們在日常開發中,應該避免在同一作用域內重複宣告。
2.this
this是一個有趣的點,this要在執行時才能確認值,定義時無法確認。
(1)作為建構函式執行
在建構函式中,會有一個this為空一開始,最後會返回那個被賦好值的this.把this看作一個變數,屬性可以擴充套件。
(2)作為物件屬性執行
當作為一個物件有某一個屬性時,this就指向那個物件。
(3)作為普通函式執行
作為普通函式,this就指向window
(4)Call apply bind
call、apply、bind的作用是改變函式執行時this的指向。
call第一個引數是this指向,後面為引數,
apply第一個引數是this指向,後面為引數陣列,
bind有點像call,同樣第一個引數是this指向,從第二個引數開始是接收的引數列表。
但他是方法.bind(物件)。這樣子的。
需要注意的是:.bind必須是一個函式表示式,不能是一個函式宣告
//建構函式
function Foo(name) {
this = {}
this.name = name
return this
}
var f = new Foo('zhangsan')
//物件屬性
var obj = {
name: 'A',
printName: function () {
console.log(this.name)
}
}
obj.printName()
//此時函式作為一個物件的屬性,this就是指向該物件
//普通函式
function fn() {
console.log(this) //this === window
//當作為普通函式執行,this就是指向window
}
fn()
//call apply bind
function fn1 (name,age) {
alert(name)
console.log(this)
}
fn1.call({x:100}, 'zhangsan', 20)
//call 第一個引數說明this指向誰,後面的引數為函式需要的引數
//apply幾乎一樣,只是把後面的引數當作陣列來傳遞
fn1.apply({x:100}, ['zhangsan', 20])
var fn2 = function (name,age) {
alert(name)
console.log(this)
}.bind({y:200})
//定義函式的時候,直接就在後面呼叫一個bind繫結一個this的物件,然後在後面就直接是執行
fn2('zhangsan',20)
//.bind必須是一個函式表示式,不能是一個函式宣告
3.作用域
(1)沒有塊級作用域
在ES6就有了,let,{}會形成塊級作用域。此處我們都講的是var。
(2)只有函式和全域性作用域
這裡即使是在函式內部宣告,也同樣會在外面讀到。
if (true) {
var name = 'zhangsan'
}
console.log('name')
//name
var name;
if (true) {
name = 'zhangsan'
}
console.log('name')
//name
4.作用域鏈
當前作用域沒有定義的變數,為自由變數
當前找不到就到函式定義的父級作用域裡面找到變數
//作用域鏈
var a = 100
function fn() {
var b = 200
console.log(a) //自由變數 到父級作用域找,函式定義時的
console.log(b)
}
fn()
//100
//200
var a = 100
function F1() {
b = 200
function F2() {
var c = 300
console.log(a) //自由變數
console.log(b) //自由變數
console.log(c)
}
F2()
}
F1()
5.閉包
函式內部還有函式。
閉包使用的場景
(1)函式作為返回值。
裡面的變數若為自由變數,就到父級作用域中去找,函式定義時的父級作用域。
bar函式作為返回值,賦值給f1變數。
(2)函式作為引數傳遞
作為引數傳遞時候,
function F1() {
var a = 100
return function bar() {
console.log(a) //自由變數,父級作用域尋找,函式定義時的父級作用域
}
}
var f1 = F1()
var a = 200
f1()
//100
function F1() {
var a = 100
return function () {
console.log(a) //自由變數,父級作用域尋找,函式定義時的父級作用域
}
}
var f1 = F1()
function F2(fn) {
var a = 200
fn()
}
F2(f1)
//100
解答:
- 說一下對變數提升的理解
1.變數定義時,會自動提升到全域性變數
2.函式宣告(注意和函式表示式的區別),函式宣告會提前,函式表示式不會提前
3.<scipt></scirpt>和函式中,要用到的變數都會被提前 - 說明this幾種不同的使用場景
1.作為建構函式執行 建構函式裡面的this一開始為空,後來賦值後返回
2.作為物件屬性執行 哪個物件使用就指向誰
3.作為普通函式執行 指向window
4.call,apply,bind 三個分別可以改變this的指向作用域。 - 建立10個<a>,點選的時候彈出來對應的序號
var i, a for(i = 0; i < 10 ; i++){ a = document.createElement('a') a.innerHTHML = i + '</br>' a.addEventListener('click', function (e) { e.preventDefault() alert(i) }) document.body.appendChild(a) } //自執行函式,就是不用呼叫,只要定義完成,就立即執行 var i for(i = 0; i < 10 ; i++){ (function (i) { var a = document.createElement('a') a.innerHTHML = i + '</br>' a.addEventListener('click', function (e) { e.preventDefault() alert(i) }) document.body.appendChild(a) })(i) }
- 如何理解作用域
1.自由變數,在這個作用域裡找不到的變數
2.作用域鏈條,即自由變數的查詢
3.閉包的兩個場景(函式作為返回值,函式作為引數) - 實際開發中閉包的應用
封裝變數,收斂許可權function isFirstLoad() { var _list = [] return function (id) { if(_list.indexOf(id) >= 0) { return false } else { _list.push(id) return true } } } var firstLoad = isFirstLoad() firstLoad(10) firstLoad(10) firstLoad(20) firstLoad(20) //你在函式外面,根本不可能修改_list的值