1. 程式人生 > >JS基礎知識(三)作用域和閉包

JS基礎知識(三)作用域和閉包

作用域和閉包

問題:

  1. 說一下對變數提升的理解

  2. 說明this幾種不同的使用場景

  3. 建立10個<a>,點選的時候彈出來對應的序號

  4. 如何理解作用域

  5. 實際開發中閉包的應用

 

知識點:

   1.執行上下文(execution context)簡稱EC
      概念:執行上下文,簡單的理解就是會提前做的一些“準備工作”。存在變數提升的現象。

     

變數:來看三個小例子,當你直接去列印一個不存在的變數,他自然會報錯,但當你在列印一個變數時,沒有提前宣告,在你需要列印的這句話下面再宣告,此時,就會顯示出準備工作的作用。會將他提前宣告,預設值為undefined

 

 this:無論在哪裡去讀取this,都會有值,但是在不同的地方不同。這個比較複雜。

 函式宣告:

此處有分別為兩點,一個是函式,另外一個是函式表示式。

函式宣告是會被提前的,但是函式表示式不會。

每一個script裡面的變數,函式都會被提前,同時還會一開始就確定thisarguments的值。

 

  1. 範圍:一段<script>或者一個函式
  2. 全域性:變數定義,函式宣告 一段<script>
  3. 函式:變數定義,函式宣告,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. 說一下對變數提升的理解
    1.變數定義時,會自動提升到全域性變數
    2.函式宣告(注意和函式表示式的區別),函式宣告會提前,函式表示式不會提前
    3.<scipt></scirpt>和函式中,要用到的變數都會被提前
  2. 說明this幾種不同的使用場景
    1.作為建構函式執行 建構函式裡面的this一開始為空,後來賦值後返回
    2.作為物件屬性執行 哪個物件使用就指向誰
    3.作為普通函式執行 指向window
    4.call,apply,bind 三個分別可以改變this的指向作用域。
  3. 建立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)
    }

     

  4. 如何理解作用域
    1.自由變數,在這個作用域裡找不到的變數
    2.作用域鏈條,即自由變數的查詢
    3.閉包的兩個場景(函式作為返回值,函式作為引數)
  5. 實際開發中閉包的應用
    封裝變數,收斂許可權
    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的值