未完待續。。。。。。 1:構造函數的使用 2:構造函數和對象的關係 3:使用JSON創建對象 4:使用構造函數創建對象 5:面向對象和麵向過程的區別 6:類的概念 7:類和對象 8:JSON字元串和對象直接的轉換 ...
一、閉包
1 . 概念:閉包就是能夠讀取其他函數內部變數的函數。在JS中,只有函數內部的子函數才能讀取局部變數,因此可以把閉包簡單理解為”定義在一個函數內部的函數”。
2 . 閉包的特點
1)可以讀取函數內部的變數。
2)讓這些變數的值始終保存在記憶體中。
3 . 閉包的原理
理解閉包,首先必須理解JS變數的作用域。變數的作用域無非就是兩種(es5):全局變數和局部變數。
JS語言的特殊之處,就在於函數內部可以直接讀取全局變數。另一方面,函數外部自然無法讀取函數內的局部變數。
註意:
1)函數內部聲明變數的時候,一定要使用var聲明。如果不用的話,你實際上聲明瞭一個全局變數。
2)局部變數的作用域,在函數定義的時候就已經確定下來了。
出於各種原因,我們有時候需要得到函數內部的局部變數。但是正常情況下這是辦不到的。只有變通一下才能實現,那就是在函數內部再定義一個函數。外部變數不能訪問內部變數,內部變數卻能訪問外部變數,這正是因為JS特有的”鏈式作用域”結構(chain scope),子對象會一級一級地向上尋找所有父對象的變數。所以父對象的所有變數,對子對象都是可見的,反之則不成立。我們只需要把子函數返回出來,我們就可以在外部讀取內部變數了。
4 . 閉包的應用場景
1)函數作為返回值。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>閉包</title>
</head>
<body>
<script>
function f1() {
var n = 999;
nAdd = function () {
n += 1;
}
function f2() {
console.log(n)
}
return f2;
}
var result = f1();
console.log("result的第一次執行")
result();//999
console.log("nAdd的執行")
nAdd();//無輸出
console.log("result的第二次執行")
result();//1000
</script>
</body>
</html>
2)函數作為參數被傳遞。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>閉包</title>
</head>
<body>
<script>
function fun(n, o) {
console.log(o);
return {
fun: function (m) {
return fun(m, n);
}
};
}
var a = fun(0); //undefined
// 執行完並未銷毀保存在記憶體中
a.fun(1); //0
a.fun(2); //0
a.fun(3); //0
fun(0).fun(1).fun(2).fun(3);
//undefined、0、1、2
var a = fun(0).fun(1);
//undefined、0
a.fun(2);
//undefined、1
a.fun(3);
//undefined、1
</script>
</body>
</html>
5 . 使用閉包註意點
1)由於閉包會使得函數中的變數都被保存在記憶體中,記憶體消耗很大,所以不能濫用閉包。否則會造成網頁性能問題,在IE中可能導致記憶體泄漏。解決方法就是在函數退出之前,將不使用的局部變數刪除(值置為null,垃圾回收機制就會處理)。
2)閉包會在父函數外部,改變父函數內部變數的值。所以不要隨便改變父函數內部變數的值。
6 . demo通過js閉包實現滑鼠滑過隔行換色的效果
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>閉包實現各行換色</title>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
h3{
text-align: center;
font-size: 24px;
line-height: 60px;
}
.newList{
width: 80%;
margin: 0 auto;
list-style: none;
}
.newList li{
text-indent: 24px;
line-height: 50px;
font-size: 16px;
border-top: 1px dashed #eeeeee;
}
</style>
</head>
<body>
<h3>新聞列表</h3>
<ul id="newList" class="newList">
<li>這是第1條新聞</li>
<li>這是第2條新聞</li>
<li>這是第3條新聞</li>
<li>這是第4條新聞</li>
<li>這是第5條新聞</li>
<li>這是第6條新聞</li>
<li>這是第7條新聞</li>
<li>這是第8條新聞</li>
<li>這是第9條新聞</li>
</ul>
</body>
<script type="text/javascript">
var oNewList=document.getElementById('newList');
var oNewListArr=Array.from(oNewList.children);
oNewListArr.forEach(function (v,i) {
// 隔行換色
if(i % 2 === 0) {
oNewListArr[i].style.background = '#f3f3f3';
};
//滑鼠滑過改變背景色
v.onmouseover=function () {
this.style.background="#87ceeb";
};
//滑鼠滑過恢複原背景色
(function (m){
oNewListArr[m].onmouseout=function () {
if(m % 2 === 0) {
oNewListArr[i].style.background = '#f3f3f3';
}
else{
oNewListArr[i].style.background = '#ffffff';
}
}
})(i);
})
</script>
</html>
二、構造函數的繼承
所謂"構造函數",其實就是一個普通函數,但是內部使用了this變數。對構造函數使用new 運算符,就能生成實例,並且this變數會綁定在實例對象上。
詳解new的執行過程:
- 在記憶體生成一個實例對象obj。
- 指定實例對象的__proto__到構造函數的prototype。
- 運行構造函數,相當於運行fn.call(obj)。
- 檢查返回值,如果返回值為基本數據類型,則無視該返回值,而將生成的對象返回。如果為引用類型,則將該返回值返回。
構造函數很好用,但是存在浪費記憶體的問題。
JS規定,每一個構造函數都有一個prototype屬性,指向另一個對象。此對象的所有屬性和方法,都會被構造函數的實例繼承。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>構造函數</title>
</head>
<body>
</body>
<script type="text/javascript">
//構造函數的命名首字母一般大寫
function Car(carlogo) {
this.carLogo = carlogo;
this.whistle = function () {
console.log("正在鳴笛......")
}
}
Car.prototype.run = function() {
console.log("正在行走......")
};
var bmw=new Car("BMW");
var audi=new Car("Audi");
console.log("構造函數創建的實例:")
console.log(bmw === audi)
console.log(bmw,audi)
console.log("構造函數whisle屬性:")
console.log(bmw.whisle === audi.whisle)
console.log("原型run方法:");
console.log(bmw.run === audi.run);
</script>
</html>
- instanceof運算符:判斷對象是不是構造函數的實例,是則true,否則false。
console.log(bmw instanceof Car)//true
console.log(bmw instanceof Array)//false
- isPrototypeOf:判斷prototype對象和某個實例之間的關係。
console.log(Car.prototype.isPrototypeOf(bmw));//true
console.log(Array.prototype.isPrototypeOf(bmw));//false
- hasOwnProperty:每個實例對象都有一個hasOwnProperty方法,用來判斷某一個屬性到底是本地屬性,還是繼承自prototype對象的屬性。
bmw.hasOwnProperty('carlogo');//true
bmw.hasOwnProperty('run');//false
- in運算符:in運算符可以用來判斷,某個實例是否含有某個屬性,不管是不是本地屬性。
console.log('run' in bmw);//true
註:
詳解instanceof 運算符。 // 會沿著原型鏈查找。
詳解hasOwnProperty方法。 // 不會沿著原型鏈查找。
詳解isPrototypeOf方法。 // 會沿著原型鏈查找。
詳解in運算符。 // 會沿著原型鏈查找。
三、call/apply繼承
call和apply都是為了改變某個函數運行時的context即上下文而存在的,即改變函數內部this的指向。
Fn.call(obj, arg1, arg2 [, argN]);
fn,.apply(obj, [arg1, arg2,…, argN]);
作用相同,apply以數組的形式傳參,call是以列表的形式。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>構造函數</title>
</head>
<body>
</body>
<script type="text/javascript">
//構造函數
function Person(name) {
this.name = name;
}
var xiaoJun = {
name: '小軍'
};
var xiaoHua = {
name: '小花',
sing: function (where, music) {
console.log(this.name + '正在' + where + '唱' + music);
}
};
xiaoHua.sing('馬路上', '葫蘆娃');
//列表形式傳參
xiaoHua.sing.call(xiaoJun, '音樂廳', '屋頂');
//數組方式傳參
xiaoHua.sing.apply(xiaoJun, ['KTV', '我們不一樣']);
//列表形式傳參,且與call、apply的執行方式不同,需要調用
var func = xiaoHua.sing.bind(xiaoJun);
func('教室', '蕩起雙槳');
</script>
</html>
apply和call實現繼承:
將父對象的構造函數綁定在子對象上,即在子對象構造函數中加一行:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>繼承</title>
</head>
<body>
</body>
<script type="text/javascript">
function Animal () {
this.eyes = 2;
}
function Dog(name) {
Animal.call(this);
this.name = name;
}
var oWangCai = new Dog('旺財');
console.log(oWangCai);
</script>
</html>
四、prototype的概念
- 一切引用類型都是對象。
- 對象是屬性的集合。
- 對象都是通過構造函數創建的。
- 每個函數都有一個prototype屬性,即原型。對象都有__proto__屬性,可以成為隱式原型。這個__proto__屬性是一個隱藏的屬性,JS並不希望開發者能夠用到這個屬性,有的低版本瀏覽器甚至不支持這個屬性值。
- 每個對象的__proto__屬性指向創建該對象的函數的prototype。
- Object.prototype.__proto__指向null。
原型鏈:
訪問一個對象的屬性時,先在本地屬性中查找,如果沒有,再沿著__proto__這條鏈向上找,這就是原型鏈。
constructor屬性:函數的prototype有一個constructor屬性,該屬性指向了函數本身。對象沒有constructor屬性,它沿著原型鏈使用的是對象的構造函數的constructor屬性。
五、this的使用情況
1:構造函數(在new的情況下)
this指向的是新構建出來的對象。
2:函數作為對象的一個屬性
函數中的this指向的是該對象。
3:函數call或者apply
當函數被call或者apply調用時,this的值就取傳入對象的值。
4:全局函數 & 普通函數
全局函數或者普通函數中,this指向的是window。
5:在prototype原型對象中,this指向的是調用構造函數實例出來的對象。
註:this關鍵字的值在函數運行的時候才會被指定。
六、原型鏈的繼承:
將一個構造函數的原型指向另一個構造函數的實例對象來實現繼承。
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>原型鏈繼承</title>
</head>
<body>
</body>
<script type="text/javascript">
function Person() {
this.age = 0;
}
function Man() {
this.beard = '鬍子';
}
var person = new Person();
//改變man的原型指向
Man.prototype = person;
var man1 = new Man();
console.log(man1.beard);
console.log(man1.age);
</script>
</html>
原型鏈的繼承必須將Man的prototype.constructor指向更改過來,否則它將會指向People,發生原型混亂。也就是下一點講述的混合繼承
七、混合繼承:
原型鏈與構造函數的優點,組合而成的一個模式,即原型鏈繼承方法,而在構造函數繼承屬性
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>混合繼承</title>
</head>
<body>
</body>
<script type="text/javascript">
function Person() {
this.age = 0;
}
Person.prototype.introduce = function () {
console.log('我有' + this.beard + ';今年' + this.age + '歲');
}
function Man() {
Person.call(this);
this.beard = '鬍子';
}
var person = new Person();
console.log("prototype.constructor指向未改:")
Man.prototype = person;//改變函數原型指向
person.age = 1;
var man1 = new Man();
var man2 = new Man();
console.log(man1.age);
console.log(man2.age);
console.log("prototype.constructor指向改變:")
Man.prototype.constructor = Man;//改變構造函數指向
var man3 = new Man();
var man4 = new Man();
man3.age = 1;
console.log(man3);
console.log(man4);
man3.introduce();
man4.introduce();
</script>
</html>