1.1 今日目標
- 能夠理解什麼是回調函數
- 能夠理解事件對象的基本含義及用法
- 能夠理解閉包的基本概念及作用
- 能夠說出作用域的訪問規則及原理
- 能夠定義JS中的直接量形式的對象
- 能夠定義js中的函數構造器
- 能夠操作JS對象的成員屬性和成員方法
- 能夠使用構造函數定義類和對象
- 能夠使用構造函數和原型鏈的混合方式定義類和對象
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>