JavaScript高級(一)

1.1 今日目標

  1. 能夠理解什麼是回調函數
  2. 能夠理解事件對象的基本含義及用法
  3. 能夠理解閉包的基本概念及作用
  4. 能夠說出作用域的訪問規則及原理
  5. 能夠定義JS中的直接量形式的對象
  6. 能夠定義js中的函數構造器
  7. 能夠操作JS對象的成員屬性和成員方法
  8. 能夠使用構造函數定義類和對象
  9. 能夠使用構造函數和原型鏈的混合方式定義類和對象

1.2 作用域鏈

1.2.1 概述

多個函數中有多個作用域,多個作用域的集合稱爲作用域鏈

<script>
var num=10;
function fun1(){
	var num2=20;
	function fun2(){
		var num3=10;
		console.log(num3);     //10
	}
	fun2();
}
fun1();
</script>

注意:變量的查找,由內往外查找。

1.2.2 特性

1、內部環境可以訪問外部環境的變量,外部的不能 訪問內部的變量

2、變量的作用域是聲明的時候決定的,與在什麼地方調用無關

<script>
var num=10;
function fun1(){
	console.log('你的數字是:'+num);
}
function fun2(){
	var num=20;
	fun1();//你的數字是:10。fun1()不管在什麼地方調用,結果都是10,與調用的地方無關。
}
fun2();		
fun1();	//你的數字是:10
</script>

1.2.3 變量訪問的優先級

<script>
var num=10;    		//外部變量
function fun(num){	//形參
	var num=30;		//內部變量
	console.log(num);
	function num(){	//內部函數
	}
}
fun(20);
//內部變量>內部函數 >形參>外部變量

小結:先加載函數,後加載變量,所以同名的變量會覆蓋同名的函數。

1.2.4 練習題

面試題一:

var num=10;
fun1();
function fun1(){
	console.log(num);	//undefined
	var num=20;	
	console.log(num);	//20
}
console.log(num);		//10

面試題二:

var num=10;
fun1();
function fun1(){
	console.log(num);	//10
	num=20;	
	console.log(num);	//20
}
console.log(num);		//20

面試題三:

var num=10;
function fun1(num){
	console.log(num);	//20
}
function fun2(){
	var num=20;
	fun1(num);
}
fun2(); 

面試題四:

var str='tom';
function fun() {
	console.log(str);	//undefined
	var str='berry';
}
fun();

面試題五:

if(!(a in window)){
	var a=1;
}
console.log(a);		//undefined
/*
全局變量都是window對象的屬性,
分析:隱式聲明瞭全局變量a,全局變量a屬於window對象,所以不執行`var a=1`。所以輸出的a是undefined。
*/

面試題六:

function fun() {
}
var fun;   //變量名和函數名同名,函數的地址自動保存到變量中
alert(fun);  //輸出變量fun,變量fun中保存了函數fun的地址。

-----------------------------------------
    
function fun() {
}
var fun=2;	
alert(fun);	//2

1.3 閉包

1.3.1 概述

閉包:兩個函數相互嵌套就形成了閉包。

形成閉包條件:

1、內部函數要訪問外部函數的變量

2、內部函數需要通過return給返回出來.

<script>
function fun1(){
	var num=10;
	function fun2(){
		console.log(num);   //條件一:內部函數要訪問外部的變量
	}
	return fun2;			//條件二:內部函數必須要return出來。
}
var str=fun1();
str();	//10
</script>

1.3.2 閉包的使用

使用一:閉包允許在全局的環境中訪問局部變量,使得訪問數據更加的安全

通全局變量統計函數調用的次數
<script>
var count=0;			//count的值可以被篡改,數據不安全
function fun(){
	count++;
	console.log('函數被調用了'+count+'次');
}
</script>
使用閉包統計
<script>
function outer(){
	var count=0;  //除了fun()可以更改count的值,沒有其他辦法可以修改count的值
	function fun(){
		count++;  
		console.log('函數被調用了'+count+'次');
	}
	return fun;
}
var str=outer();
</script>

使用二:每調用一次閉包,內存就劃分一個空間。同一個閉包可以創建多個空間,每個閉包(空間)是獨立的。

思考如果代碼輸出什麼

<script>
var arr=new Array();
for(var i=0;i<=4;i++){
	arr[i]=function(){
		console.log(i);
	}
}
arr[0]();	//5    訪問的是同一個i
arr[1]();	//5
arr[2]();	//5
</script>

思考如果代碼輸出什麼

<script>
function fun1(num){
	function fun2(){
		console.log(num);
	}
	return fun2;
}

var arr=new Array();
for(var i=0;i<=4;i++){
	arr[i]=fun1(i);
}
arr[0]();	//0   不同的閉包函數中保存的不同的i
arr[1]();	//1
arr[2]();	//2
</script>

使用三:閉包可以使得局部變量一直駐留在內存中,不被銷燬。

<script>
function initCard(name){
	var money=0;
	function saveMoney(num){
		money+=num;
		console.log(name+'你好:您存'+num+'元,餘額是:'+money);
	}
	function getMoney(num){
		money-=num;
		console.log(name+'你好:您取'+num+'元,餘額是:'+money);
	}
	return {
		saveMoney:saveMoney,
		getMoney:getMoney
	} 
}
//測試
var p1=initCard('tom');
p1.saveMoney(1000);		//tom你好:您存1000元,餘額是:1000
p1.getMoney(100);		//tom你好:您取100元,餘額是:900
</script>

小結:閉包可以在全局的環境下訪問局部變量,每次調用閉包都會在內存劃分空間,調用完畢後空間不銷燬。

例題:

<script>
window.onload=function(){
	var aLi=document.getElementsByTagName('li');
	//方法一:通過this
	/*
	for(var i=0;i<aLi.length;i++){
		//鼠標移上去
		aLi[i].οnmοuseοver=function(){
			this.style.backgroundColor='#ccc';
		}
		//鼠標移走
		aLi[i].οnmοuseοut=function(){
			this.style.backgroundColor='#fff';
		}
	}
	*/
	//方法二:閉包
	for(var i=0;i<aLi.length;i++){
		aLi[i].onmouseover=setStyle(i,'#ccc');
		aLi[i].onmouseout=setStyle(i,'#fff');
	}

	function setStyle(i,color){
		function fun(){
			aLi[i].style.backgroundColor=color;
		}
		return fun;
	}
}
</script>
<ul>
	<li>鋤禾日當午</li>
	<li>汗滴禾下土</li>
	<li>誰知盤中餐</li>
	<li>粒粒皆辛苦</li>
</ul>

運行結果

在這裏插入圖片描述

1.3.3 閉包的內存管理

閉包占用的內存是不會釋放的,如果濫用閉包,會造成內存泄漏

內存泄漏:程序己動態分配的內存未釋放或無法釋放,導致程序運行速度減慢甚至系統崩潰等後果。

1.4 面向對象

1.4.1 概述

面向過程:面向的是動作,站在執行者的角度

面向對象:面向實現動作的對象,站在指揮者的角度,對象的方法中封裝的面向過程。

1.4.2 面向過程到面向對象演化

第一步:面向過程

<style>
	div{
		width: 200px; height: 50px; margin-bottom: 10px;
	}
</style>
<div></div>
<div></div>
<script>
    //第一步:獲取節點
	var div=document.querySelectorAll('div');
    //第二步:給節點添加樣式
	for(var i=0;i<div.length;i++){
		div[i].style.border='1px solid #d00';
	}
</script>

運行結果

在這裏插入圖片描述

第二步:封裝函數

<script>
    //第一步:獲取節點
    function getElements(selector){
    	return document.querySelectorAll(selector);
    }
    //第二步:給節點添加樣式
    function setBorder(elem){
    	for(var i=0;i<elem.length;i++){
			elem[i].style.border='1px solid #d00';
		}
    }
	//第三步:調用
	var elem=getElements('div');
	setBorder(elem);
</script>

第三步:封裝對象

<script>
//封裝對象
var obj={
	//第一步:獲取節點
    getElements:function(selector){
    	return document.querySelectorAll(selector);
    },
    //第二步:給節點添加樣式
    setBorder:function(elem){
    	for(var i=0;i<elem.length;i++){
			elem[i].style.border='1px solid #d00';
		}
    }
}    
//第三步:調用
var elem=obj.getElements('div');
obj.setBorder(elem);
</script>

1.5 創建對象

1.5.1 創建字面量對象

字面量對象就是字面本身,字面量對象裏面都是鍵值對。

<script>
var obj={}	//聲明字面量對象
console.log(obj);
//字面量對象裏面都是鍵值對
var stu={name:'tom',sex:'男',exam:function(){alert('學生要考試')}};
console.log(stu.name);		//調用成員
console.log(stu['name']);	//調用成員
stu.exam();
//給字面量對象添加屬性
stu.add='上海';
stu.eat=function(){
	console.log('學生要喫飯');
}
</script>

小結:

1、字面對象就是字面本身,用{}表示

2、字面量對象由鍵值對組成

3、調用成員方法: 對象.成員 對象[‘成員’]

4、可以給字面量對象添加成員

1.5.2 實例化Object創建對象

<script>
	//實例化對象
	var stu=new Object();
	console.log(stu);		//{}
	//給對象添加成員
	stu.name='tom';
	stu.eat=function(){
		console.log('學生要喫飯');
	}
	//調用成員
	console.log(stu.name);	//tom
	stu['eat']();		//學生要喫飯
</script>

注意:Object在JS中最頂層的父類。

1.5.3 工廠模式創建對象

批量創建對象

<script>
function createStu(name,sex){
	var stu={};
	stu.name=name;
	stu.sex=sex;
	return stu;
}
var stu1=createStu('tom','男');
var stu2=createStu('berry','女');
console.log(stu1);	//{name: "tom", sex: "男"}
console.log(stu2);	//{name: "berry", sex: "女"}
</script>

1.5.4 實例化構造器創建對象

在JS中沒有類的概念,用構造器充當類的構造方法。

<script>
function Student(name,n){
	this.name=name;		//姓名,要實例化必須寫this
	this.subject=n;		//考試科目
	this.exam=function(){
		console.log('學生要考試');
	}
}
//測試
var stu=new Student('tom',2);
console.log(stu);	//Student {name: "tom", subject: 2, exam: ƒ}
</script>

注意:構造函數和普通函數沒有區別,如果是new那就是構造函數

提醒:每次實例化都會分配一個exam空間,這樣是浪費空間的

<script>
function Student(){
	this.exam=function(){
		console.log('學生要考試');
	}
}
var stu1=new Student();
var stu2=new Student();
console.log(stu1.exam==stu2.exam);   //false
</script>

1.4 方法共享

方法一:

<script>
function Student(){
	Student.exam=function(){		//類似於添加靜態方法,方法屬於類的成員

	}
}
var stu1=new Student();
var stu2=new Student();
console.log(stu1.exam==stu2.exam);	//true
</script>

方法二:

<script>
function exam(){

}
function Student(){
	this.exam=exam;		//將函數exam的地址保存到屬性中
}
var stu1=new Student();
var stu2=new Student();
console.log(stu1.exam==stu2.exam);	//true
</script>

上面兩種方法可以解決內存浪費的問題,但會造成“全局污染”。

全局污染:A頁面引入了B頁面,如果兩個頁面中有同名的變量,兩個變量相互影響稱爲全局污染。在開發中儘量少使用全局變量。全局變量容易造成全局污染。

方法三:

<script>
var obj={
	exam:function(){		//將全局的exam封裝到對象中。
	}
}

function Student(){
	this.exam=obj.exam;		
}
var stu1=new Student();
var stu2=new Student();
console.log(stu1.exam==stu2.exam);	//true
</script>

腳下留心:以上三種方法也不是最好的方法,真正解決的方法是“原型”。

1.5 原型

1.5.1 prototype

每個函數都有一個prototype屬性,prototype屬性中保存了一個對象,這個對象就稱爲原型。

<script>
function Student(){
}
console.log(Student.prototype);    //{constructor: ƒ}
</script>

原型作用:只要是通過構造函數創建的對象都可以訪問原型上的成員。

例題

<script>
function Student(){
}
Student.prototype.name='tom';		//給原型對象添加name屬性
Student.prototype.exam=function(){	//給原型對象添加exam方法,可以防止全局污染
	console.log('學生要考試');
}
//console.dir(Student.prototype);	//console.dir()打印對象上所有的成員
var stu1=new Student();
var stu2=new Student();
console.log(stu1.name,stu2.name);
console.log(stu1.exam==stu2.exam);		//true
</script>

多學一招:對象的內置方法都存在原型上

<script>
console.log([]==[]);			//false
console.log([].push==[].push);	//true
console.log(Array.prototype);	//數組的內置方法都保存在Array的原型中
</script>

1.5.2 __proto__

任何對象都有__proto__屬性

對象的__proto__屬性保存的構造函數的原型

<script>
function Student(){
}
var stu=new Student();
console.log(Student.prototype==stu.__proto__);	//true
</script>

在這裏插入圖片描述

小結:獲取原型的方法有兩種

1、構造器的.prototype屬性

2、對象的.__proto__屬性

1.6 構造器(constructor)

每個原型都有一個constructor屬性,指向構造函數

對象、構造器、原型的關係

在這裏插入圖片描述

<script>
function Student(){
}
var stu=new Student();
console.log(stu.__proto__.constructor==Student);	//true
console.log(Student.prototype.constructor)			//Student
</script>

1.7 原型鏈

每個對象都有__proto__屬性指向它的原型對象,原型對象也有__proto__屬性指向了自己的原型對象,形成一個鏈式結構稱爲原型鏈

<script>
function Student(){
}
console.log(Student.prototype);						// ƒ Student()
console.log(Student.prototype.__proto__.constructor);	//ƒ Object()
console.log(Student.prototype.__proto__);		//ƒ Object()
console.log(Student.prototype.__proto__.__proto__);		//null
</script>

在這裏插入圖片描述

練習一:畫出Array的原型鏈

在這裏插入圖片描述

練習二:畫出Math的原型鏈

Math不是構造函數,是對象

<script>
new Math();		//Uncaught TypeError: Math is not a constructor
</script>

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章