11、數(shù)據(jù)類型檢測與toString方法的理解
1,typeof value (檢測一個值的類型:原始類型或者引用類型)
檢測當前值的數(shù)據(jù)類型,返回的結(jié)果:首先是一個字符串,其次字符串中包含了對應(yīng)的數(shù)據(jù)類型
[TYPEOF局限]
1、typeof null->"object" null為空對象指針 但是null不是對象數(shù)據(jù)類型;
2,不能具體細分是數(shù)組還是正則還是對象中的其他值,因為使用typeof檢測數(shù)據(jù)類型對于對象數(shù)據(jù)類型的值返回的都是"object"
BAT面試題:
console.log(typeof typeof []);:
-> typeof [] =>"object"
-> typeof "object" =>"string"
2,A instanceof B
檢測A實例是否屬于B這個類(可以具體細分一個對象是數(shù)組還是正則)A必須是對象數(shù)據(jù)類型,只要B在A的原型鏈上出現(xiàn)過,檢測結(jié)果就是true;
[instanceof局限性]
1,不能用來檢測和處理字面量方式創(chuàng)建出來的基本數(shù)據(jù)類型值;
- 對于基本數(shù)據(jù)類型來說,字面量方式創(chuàng)建出來的結(jié)果和實例方式創(chuàng)建出來的結(jié)果是有一定區(qū)別的,從嚴格意義上來講,只有實例創(chuàng)建出來的結(jié)果才是標準的對象,數(shù)據(jù)類型值也是標準的基本數(shù)據(jù)類型,也是標準的內(nèi)置類的實例;對于字面量方式創(chuàng)建出來的結(jié)果是基本數(shù)據(jù)類型的值,不是嚴格的實例,但是由于JS的松散特點,導致了可以使用 內(nèi)置類.prototype上提供的方法;
2,只要在原型鏈上能找到就返回true,所以在類的原型繼承中,我們最后用instanceof檢測出來的結(jié)果未必準確
3,constructor
獲取當前實例所屬類的構(gòu)造函數(shù)的屬性 與instanceof檢測類似 可以處理基本數(shù)據(jù)類型的檢測,一般檢測不了object數(shù)據(jù)類型;
[constructor局限性]
1,我們可以把類的原型進行重寫,在重寫的過程中很有可能出現(xiàn)把之前的constructor給覆蓋了,這樣檢測出來的結(jié)果就是不準確的
4,Object.prototype.toString.call().slice(8,-1);
最常用最準確的,借用Object基類原型上的toString方法,在執(zhí)行這個方法的時候,讓方法中的THIS指向需要檢測的值,從而獲取到當前值所屬類的詳細信息,進而檢測出對應(yīng)的數(shù)據(jù)類型
原理:Object.prototype.toString它的作用是返回當前方法的執(zhí)行主體(方法中的this)所屬類的詳細信息
var obj={name:"珠峰"};
console.log(obj.toString());toString中的this是obj,返回的是obj所屬類的信息
"[object Object]"
- 第一個object代表當前實例是對象數(shù)據(jù)類型的(固定的)
- 第二個Object代表的是obj所屬的類是Object
toString的理解:
1,對于Number、String、Boolean、Array、RegExp、Date、Function原型上的toString方法都是把當前數(shù)據(jù)類型轉(zhuǎn)換為字符串的類型(它們的作用僅僅是用來轉(zhuǎn)換為字符串的)要把Object類型的值轉(zhuǎn)換為字符串的話可以用JSON.stringify(Object),例如JSON.stringify({name:"珠峰"})-》{"name":"珠峰"}
2,Object.prototype.toString方法并不是用來轉(zhuǎn)換為字符串的({name:"珠峰"}).toString()->"[object Object]"
模擬內(nèi)置isArray所寫檢測數(shù)據(jù)類型的方法:
//根據(jù)老師的方法修改版,親測可用
~function () {
var obj = {
isNumber: 'Number',
isString: 'String',
isBoolean: 'Boolean',
isNull: 'Null',
isUndefined: 'Undefined',
isPlanObject: 'Object',
isArray: 'Array',
isRegExp: 'RegExp',
isFunction: 'Function'
};
var check = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
//給check對象添加每個類的檢測方法名和檢測方法
check[key] = (function (value) {
return function (val) {
return value===(Object.prototype.toString.call(val).slice(8,-1));
}
})(obj[key]);
}
}
window.check = check;
}();
12、null和undefined的區(qū)別匯總
null==undefined =>true
null===undefined =>false
0和空字符串與null和undefined的區(qū)別:
null和undefined相比于0和空字符串占用的內(nèi)存更少
null:
空對象指針->沒有具體的值->一般都是我們手動設(shè)置初始值為null,后期會給其賦值;
undefined:
未定義->連東西都沒有->一般都是瀏覽器默認的值;
用null的幾種情況:
1,設(shè)定變量初始值:我們設(shè)定一個變量,后期我們要使用,那么我們設(shè)置默認值為null
2,釋放堆內(nèi)存:在JS內(nèi)存釋放中,我們想釋放一個堆內(nèi)存,就讓其值變?yōu)閚ull即可
3,在DOM元素獲取中,不存在則結(jié)果為null:我們通過DOM中提供的屬性和方法獲取頁面中的某一個元素標簽,如果這個標簽不存在,獲取的結(jié)果是null,而不是undefined
4,在正則的exec/字符串的match捕獲中,如果當前要捕獲的字符串和正則不匹配的話,捕獲到的結(jié)果為null;
用undefined的集中情況:
1,JS預解釋的時候只聲明未定義,默認值為undefined;
2,在一個函數(shù)中,如果沒有寫return,或者return后什么都沒寫,默認返回值為undefined;
3,函數(shù)中設(shè)置了形參,但是執(zhí)行的時候如果沒有傳遞參數(shù)值,那么形參默認值為undefined;
4,獲取一個對象的屬性名對應(yīng)屬性值的時候,如果這個屬性名不存在的話屬性值為undefined;
5,嚴格模式下,this前沒有明確的執(zhí)行主體,this就是undefined
6,用來檢測瀏覽器兼容問題,不兼容的話返回undefined
13、函數(shù)數(shù)據(jù)類型
函數(shù)
函數(shù):具備一定功能的方法;
<font color=red>創(chuàng)建函數(shù):</font>
1.聲明一個函數(shù)名(函數(shù)用function,變量用var)
2.開辟一個新的內(nèi)存空間(有一個16進制的地址),然后把函數(shù)體中實現(xiàn)功能的JS代碼按照"字符串"的格式存儲在內(nèi)存中
3.把空間地址賦值給函數(shù)名,此時函數(shù)名就可以和函數(shù)體本身關(guān)聯(lián)到一起了;
function 函數(shù)名(){
函數(shù)體:實現(xiàn)某一功能的JS代碼
}
<font color=red>函數(shù)執(zhí)行:</font>
1.函數(shù)執(zhí)行的時候會形成一個私有的作用域,提供一個環(huán)境供函數(shù)體中的代碼執(zhí)行;
2.把創(chuàng)建的時候存儲的字符串變?yōu)檎嬲腏S代碼,在私有作用域中自上而下執(zhí)行
<font color=red>注意:</font>一個函數(shù)可以被執(zhí)行N次,每一次執(zhí)行相互之間互不干擾(后面會學習兩者之間建立的間接關(guān)系)
形參:形式參數(shù)(變量),函數(shù)的入口,形參也相當于創(chuàng)建了一個私有變量
實參:函數(shù)執(zhí)行傳遞給函數(shù)的具體值就是實參
函數(shù)本身也會有一些自己的屬性
-->length:0 形參的個數(shù)
-->name:"Fn" 函數(shù)名
-->prototype 類的原型,在原型上定義的方法都是當前Fn這個類實例的公
有方法
-->_proto_ 把函數(shù)當做一個普通的對象,指向Function這個類的原型
call apply bind
call
fn.call(context,para1,…)
<font color=red>在借用數(shù)組原型上的方法把類數(shù)組轉(zhuǎn)化為數(shù)組的時候,IE低版本瀏覽器中,元素集合或者節(jié)點集合這些類數(shù)組是無法借用slice轉(zhuǎn)換的會報錯“不是Javascript對象”;</font>
Function.prototype上的call方法的實現(xiàn)原理
Function.prototype.call=function(context){
//先明確此方法中的this指向的是call點前面的執(zhí)行主體,即調(diào)用此方法的主體
1、讓此方法中的this(call點前面的執(zhí)行主體)里面的this關(guān)鍵字指向context
2、讓此方法中的this(call點前面的執(zhí)行主體)執(zhí)行;
//以下為第二步:讓this執(zhí)行,即call點前面的執(zhí)行主體執(zhí)行;
this()
}
function fn1(){
console.log(1);
}
function fn2(){
console.log(2);
}
fn1.call(fn2);//1
fn1.call.call.call.call(fn2);//2
把方法執(zhí)行,并且讓方法中的this改變?yōu)閏ontext,都是給fn傳遞的實參
//非嚴格模式下
function fn(){
console.log(this);
}
fn.call(1);//1
fn.call();//不傳遞參數(shù)時默認為window
fn.call(null);//傳遞null也為window
fn.call(undefined);//傳遞undefined也為window
//JS嚴格模式下
如果不傳則默認為undefined
傳的話傳的是什么就修改為什么;
apply
apply的語法和call基本一致,作用原理也基本一致,唯一的區(qū)別:apply把傳遞給函數(shù)的實參以數(shù)組形式存放(但是也相當于在給函數(shù)一個個的傳遞實參值)
bind(bind方法返回的是一個新的函數(shù)和原函數(shù)空間地址不同);
第一個參數(shù)為要改變的執(zhí)行主體中的this關(guān)鍵字,
第一個之后的參數(shù)都是執(zhí)行主體執(zhí)行的時候需要傳遞給執(zhí)行主體的參數(shù),傳遞給執(zhí)行主體的參數(shù)會放在執(zhí)行主體自帶的參數(shù)之前;
改變函數(shù)中this關(guān)鍵字,在IE6-8下不兼容;它和call(以及apply)改變this的原理不一樣
預先讓fn中的this指向opp,此時fn沒有執(zhí)行,只有fn執(zhí)行的時候才起作用;
bind方法兼容IE6-8版
var flag='getComputedStyle' in window;
Function.prototype.myBind=function (context) {
context=context||window;
var that=this;
var outerArg=Array.prototype.slice.call(arguments,1);
//標準瀏覽器下
if(flag){
return this.bind.apply(this,arguments);
}
//IE6-8下
return function () {
//定義一個變量接收形參事件對象
var innerArg=Array.prototype.slice.call(arguments);
that.apply(context,outerArg.concat(innerArg));
}
}
例如:點擊盒子的時候,執(zhí)行fn,并讓其中的this改變?yōu)閛pp
function fn(){
console.log(this);
}
oBox.onclick=fn.call(opp);/無法實現(xiàn),還沒有點擊的時候,fn已經(jīng)執(zhí)行了,只是把fn的返回結(jié)果給了onclick
oBox.onclick=fn.bind(opp);//完美實現(xiàn),點擊的時候fn才執(zhí)行,并預先讓fn中的this變?yōu)閛pp;
在真實項目中,我們把實現(xiàn)一個具體功能的代碼封裝在函數(shù)中:
好處:
1:減少了冗余代碼,開發(fā)效率高;
2:封裝在一個函數(shù)中,頁面中就基本上很難出現(xiàn)重復一樣的代碼了,減少了頁面中代碼的冗余度,提高了代碼的重復利用率:低耦合高內(nèi)聚;
我們把以上的特點稱之為函數(shù)封裝(OOP面向?qū)ο缶幊趟枷?,需要我們掌握的就是類的繼承、封裝、多態(tài))
函數(shù)的核心原理
函數(shù)作為引用數(shù)據(jù)類型的一種,也是按照地址來操作的,私有作用域中不帶var的聲明都是給window設(shè)置的屬性;
函數(shù)存在了多面性
1)作為普通函數(shù)(fn()):它本身就是一個普通的函數(shù),執(zhí)行的時候形成私有的作用域(閉包),形參賦值,預解釋,代碼執(zhí)行,執(zhí)行完成后棧內(nèi)存銷毀/不銷毀
2)作為類,即構(gòu)造函數(shù)(new Fn();):它有自己的實例,也有一個叫做prototype屬性是自己的原型,它的實例都可以指向自己的原型
3)作為普通對象(fn.aaa):和var obj={}中的obj一樣,就是一個普通的對象,它作為對象可以有一些自己的私有屬性,也可以通過____proto____找到Function.prototype
- 所有的函數(shù)都可以調(diào)取Function.prototype(類也是函數(shù))上的方法:如call、apply、bind
- 所有的對象都可以調(diào)取Object.prototype(函數(shù)也是對象)上的方法:如toString、hasOwnProperty
普通函數(shù)執(zhí)行和構(gòu)造函數(shù)執(zhí)行的區(qū)別
構(gòu)造函數(shù)執(zhí)行的時候,也是先形成一個私有作用域,形參賦值,變量提升,在代碼從上而下執(zhí)行之前,構(gòu)造函數(shù)有特殊的操作:
瀏覽器會在當前的作用域中默認創(chuàng)建一個對象數(shù)據(jù)類型的值,并且會讓構(gòu)造函數(shù)中的this指向創(chuàng)建的這個對象。然后JS代碼再執(zhí)行,代碼執(zhí)行完成后,即使函數(shù)中沒有寫return,在構(gòu)造函數(shù)模式中:瀏覽器會默認的把創(chuàng)建的對象返回到函數(shù)外面
<font color=red>總結(jié):</font>
- 構(gòu)造函數(shù)執(zhí)行期間,既具備函數(shù)執(zhí)行的一面,也同時具備自己獨有的操作:在構(gòu)造函數(shù)執(zhí)行期間,瀏覽器會默認創(chuàng)建一個對象,這個對象就是當前這個構(gòu)造函數(shù)(類)實例,函數(shù)執(zhí)行完成后,瀏覽器會默認的把這個實例返回。所以new Fn()執(zhí)行,F(xiàn)n是一個類,返回的結(jié)果就是Fn這個類的一個實例。
阿里面試題執(zhí)行順序總結(jié)
new Foo.getName();
new Foo().getName();
new new Foo().getName();
執(zhí)行順序總結(jié):(經(jīng)驗之談目前來說準確)
<font color=red>1、new 操作符只能new構(gòu)造函數(shù),否則會報錯,xxx is not a constructor
2、new碰到()就會把new與()之間的當成構(gòu)造函數(shù)處理,所以會先執(zhí)行new與()之間的運算,之后再new。<font>
棧內(nèi)存:
- 作用域(全局/私有作用域):提供一個供JS代碼執(zhí)行的環(huán)境;執(zhí)行JS代碼的
- 基本數(shù)據(jù)類型的值是直接存放在棧內(nèi)存中的
銷毀:
+ 一般情況下,函數(shù)執(zhí)行形成棧內(nèi)存,函數(shù)執(zhí)行完,瀏覽器會把形成的棧內(nèi)存自動釋放;有時候執(zhí)行完成,棧內(nèi)存不能被釋放。
全局作用域在加載頁面的時候執(zhí)行,在關(guān)掉頁面的時候銷毀;
堆內(nèi)存:
只要遇到對象數(shù)據(jù)類型或函數(shù)數(shù)據(jù)類型,瀏覽器就會創(chuàng)建一個堆內(nèi)存。存儲引用數(shù)據(jù)類型值的
所有的引用數(shù)據(jù)類型,他們需要存儲的內(nèi)容都放在堆內(nèi)存中(相當于一個倉庫,目的是存儲信息的)
- 對象會把鍵值對儲存起來
- 函數(shù)會把JS代碼當做字符串儲存起來
- 銷毀:
- var o={}; 當前對象對應(yīng)的堆內(nèi)存被變量o占用著呢,堆內(nèi)存是無法銷毀的
- o=null; null空對象指針(不指向任何的堆內(nèi)存),此時上一次的堆內(nèi)存就沒有被占用了,谷歌瀏覽器會在空閑時間把沒有被占用的堆內(nèi)存自動釋放(銷毀/回收)
定義變量的時候帶var和不帶var的區(qū)別?
在全局作用域中,帶不帶var都一樣,都相當于聲明了一個全局變量,給全局對象設(shè)置了一個新的屬性名,但是不帶var的不能提前聲明,所以在賦值前不能提前調(diào)用;
在局部作用域中,帶var的話聲明的都是私有變量,不帶var的話聲明的都是全局變量,也相當于給window設(shè)置了一個屬性,也不能提前調(diào)用。
普通函數(shù)執(zhí)行步驟:形成一個私有作用域
1.形參賦值
2.變量提升(形參賦值后,如果有函數(shù)聲明且與形參名字相同,則覆蓋形參的值,var聲明相當于重復聲明,不會覆蓋形參的值)
3.代碼從上到下執(zhí)行
4.棧內(nèi)存銷毀、不銷毀;
變量提升
在
當前作用域中,JS代碼從上而下執(zhí)行之前,瀏覽器會把所有帶var和function關(guān)鍵字的進行提前聲明,函數(shù)聲明和定義同時完成;(只對當前作用域下的變量或者函數(shù)起作用)
預解釋:
1,發(fā)現(xiàn)重復的,不重復聲明,只定義,重復定義只會替換之前的值;(函數(shù)聲明加定義一起完成)
2,預解釋不管條件;(在最新的瀏覽器(IE11及以上),不管條件成立與否都會提前聲明,不會提前定義,然后再看條件是否成立,如果成立則看有沒有函數(shù),如果有函數(shù),則先定義,如果沒有,JS代碼從上到下執(zhí)行成立的代碼。如果不成立,則走不成立的代碼)IE10及以下(不管條件是否成立函數(shù)都會進行聲明+定義);
3,只對等號左邊的進行變量提升,右邊是值,不會提前聲明什么的;
4,預解釋發(fā)生在同一個腳本塊中。
5,return 后面跟著的是值,不會進行預解釋,但是return下面的代碼要進行預解釋;
所有聲明變量或聲明函數(shù)都會被提升到當前函數(shù)的頂部。
作用域鏈
在私有作用域中
聲明的變量和函數(shù)的形參都是私有的變量;
在私有作用域中,代碼執(zhí)行的時候遇到一個變量,首先看它是否為私有變量:
[是私有變量]
- 則和外面沒有任何關(guān)系,以后在這個作用域中操作的當前變量都按照私有的處理;
[不是私有變量]
- 則往當前作用域的上級作用域進行查找,如果上級作用域中有,我們操作的都是上級作用域的中的變量(假如我們在當前作用域把值改了,相當于把上級作用域中的這個值給修改了)。如果上級作用域也沒有,則繼續(xù)往上查找,一直照到window為止,這就是作用域鏈;
- 如果找到window下也沒有,分兩種情況:**
- 我們是獲取值:console.log(total);->報錯,下面代碼不再執(zhí)行
- 我們是設(shè)置值:total=100;->相當于給window增加了一個屬性名叫total,屬性值為100;
如何查找函數(shù)的上級作用域?
<font color=red>看當前函數(shù)是在哪個作用域下定義的,那么它的上級作用域就是誰,和函數(shù)在哪執(zhí)行沒有任何關(guān)系;<font>
只有函數(shù)執(zhí)行的時候會產(chǎn)生私有作用域;
console.log(x,y);//undefined undefined
var x=10,
y=20;
function fn() {
console.log(x,y);//undefined 20(為什么不是100而是20,因為下一行的y=100還沒有執(zhí)行 哈哈哈被騙了吧)!
var x=y=100;
console.log(x,y);//100 100
}
fn();
console.log(x,y);//10 100 為什么x是10而不是100,因為閉包里的x=100外界無法拿到;
作用域是否銷毀
堆內(nèi)存
1.對象數(shù)據(jù)類型或者函數(shù)數(shù)據(jù)類型在定義的時候首先都會先開辟一個堆內(nèi)存,堆內(nèi)存有一個引用地址,如果有變量或者元素事件知道了這個地址,我們就說這個堆內(nèi)存被占用了,那么就要考慮這個堆內(nèi)存是否會銷毀;
棧內(nèi)存
全局作用域:
- 只有當頁面關(guān)閉的時候全局作用域才會銷毀。
私有作用域
一般來說,函數(shù)體中的代碼執(zhí)行完成,形成的棧內(nèi)存會立即釋放(自執(zhí)行函數(shù)也不例外),當然也有不釋放的。
- 當私有作用域中的堆內(nèi)存的地址被作用域以外的東西占用了,那么當前的這個作用域就不能被銷毀(下面為三種情況)
- 函數(shù)執(zhí)行形成一個私有作用域,如果私有作用域中的部分內(nèi)容被以外的變量占用了,當前作用域不銷毀
如:函數(shù)返回了一個引用數(shù)據(jù)類型的值,而且在外面有一個變量接受了這個返回值,此時當前作用域就不能銷毀,想要銷毀只需把外面的變量賦值為null,即解除占用即可;
如果返回的是一個基本數(shù)據(jù)類型的值,而且外面有一個變量接收,當前私有作用域是否會銷毀?
會銷毀,因為返回引用數(shù)據(jù)類型的值會在私有作用域中開辟一個堆內(nèi)存,而且這個堆內(nèi)存被外面的變量或元素元素事件占用著,所以這個堆內(nèi)存不銷毀,進而導致這個堆內(nèi)存所在的私有作用域也不會銷毀。而基本數(shù)據(jù)類型值是直接把值拷貝一份給外面的變量,所以返回完成后,函數(shù)中的堆內(nèi)存沒有被占用,就會被銷毀掉,形成的私有作用域也會被銷毀掉;
- 在一個私有的作用域中給DOM元素的事件綁定方法,一般情況下我們的私有作用域都不銷毀
- 下述情況屬于不立即銷毀
函數(shù)執(zhí)行返回一個函數(shù)沒有被其他東西占用,但是還需要執(zhí)行一次,所以暫時不銷毀,當返回的函數(shù)執(zhí)行完成后,瀏覽器會在空閑的時候把它銷毀了;
<font color=red>總結(jié):作用域是否銷毀就看兩步:
- 1、這個作用域是否產(chǎn)生了堆內(nèi)存
- 2、這個作用域產(chǎn)生的堆內(nèi)存是否被這個作用域以外的變量或事件占用了,如果被占用了,那么這個堆內(nèi)存不能被銷毀,進而導致這個私有作用域也不能被銷毀。<font>
如果產(chǎn)生的堆內(nèi)存被自身作用域中的變量占用了,那么這個堆內(nèi)存會在被使用后銷毀,所在的私有作用域也會被銷毀
閉包
函數(shù)執(zhí)行會形成一個私有的作用域,保護私有變量不受外部影響,從外部拿不到里面的變量,此時我們可以理解為私有作用域把私有變量保護了起來,這種保護機制稱之為'閉包'。由方法運行而產(chǎn)生的私有作用域就叫閉包,為了讓變量更安全。閉包是一種機制而不是某種形式;
閉包面試總結(jié):
閉包就是連接私有作用域和外部作用域之間的一座橋梁,它不僅能保護私有變量不受外界干擾,還能保存一些內(nèi)容,而且還能把私有作用域中的東西拿到外部使用。如果我想在外部使用閉包中的值怎么辦?
1.在閉包中設(shè)置返回值,在外部聲明一個變量接收。
2.把閉包中的值賦值給window的一個屬性。
閉包的作用
1.保護
形成私有作用域,保護里面的私有變量不受外界的干擾,真實項目中,我們利用這種保護機制,實現(xiàn)團隊協(xié)作開發(fā)(避免了多人同一個命名,導致代碼沖突的問題)
- jQuery:常用的JS類庫,提供了很多項目中常用的方法(兼容所有瀏覽器)
- Zepto:小型JQ,專門為移動端開發(fā)準備的
//JQ代碼片段
(funciton(window)){
var jQuery=function(){
....
}
....
window.jQuery=window.$=jQuery;
}(window);
jQery()
$()
//Zepto代碼片段
var Zepto=(function(){
var Zepto=funciton(){
...
};
....
return Zepto;
})();
var $=Zepto
Zepto();
$();
真實項目中,我們利用這種保護機制,實現(xiàn)團隊協(xié)作開發(fā)(避免了多人同一個命名,導致代碼沖突的問題)
//A
(funciton()){
//A寫的代碼
function fn(){
....
}
....
window.fn=fn;
}();
//B
(function(){
//B寫的代碼
funciton fn(){
...
};
//B想要調(diào)取A寫的fn
window.fn();
})();
2.保存
函數(shù)執(zhí)行形成一個私有作用域,函數(shù)執(zhí)行完成,形成的這個棧內(nèi)存一般情況都會自動釋放
但是當棧內(nèi)存中的內(nèi)容被棧內(nèi)存以外的其他東西(變量/元素的事件)占用了,那么這個棧內(nèi)存就不能被釋放掉,也就形成了不銷毀的私有作用域(里面的私有變量也不會銷毀);
應(yīng)用:高級單例模式;
i++與++i的區(qū)別:
i++,先拿原有的值和其他值運算,運算后再累加1;
++i,先累加1,再拿結(jié)果進行運算;
++i和i=i+1的區(qū)別
++i在拿到i的值的時候,會先用Number()轉(zhuǎn)化一下,然后再累加1,之后再運算,而i=i+1不會;
函數(shù)的中的this問題
當前函數(shù)的執(zhí)行主體,
this指向是函數(shù)執(zhí)行時決定的 不是編寫代碼時指定。this的幾種情況:
全局中的this是window,我們都研究函數(shù)內(nèi)部的this的指向問題
在JS的非嚴格模式下
- 1.自執(zhí)行函數(shù)中的this都是window
- 2.給元素的某個事件綁定方法,當事件觸發(fā)執(zhí)行對應(yīng)方法的時候,方法中的this一般都是當前操作的元素本身(在IE6-8下DOM2級事件用attachEvent綁定方法時,方法中的this是window,而不是當前操作元素本身)
- 3.函數(shù)執(zhí)行前的主體
+ fn(); this為windows
+ obj.fn(); this為obj
- 4.ES6中箭頭函數(shù)中的this繼承宿主環(huán)境中的this:看方法在哪定義的,宿主環(huán)境就是誰,箭頭函數(shù)中的this就是宿主環(huán)境中的this;
var obj={
fn:function(){
//=>this:obj
setTimeout(function(){
//=>this:window 不管在哪執(zhí)行,定時器中的this是window
},1000);
//=>想讓定時器函數(shù)中的this也是obj
setTimeout(function(){
//=>this:obj
}.bind(this),1000);
var _this=this;
setTimeout(function(){
//=>_this:obj
_this.name
='xxx';
},1000);
setTimeout(()=>{
//=>this:obj 箭頭函數(shù)中的this繼承宿主環(huán)境(上級作用域中)的this
},1000);
}
};
obj.fn();
在JS嚴格模式下(讓JS更加嚴謹)
開啟嚴格模式:在當前作用域的第一行加 "use strict"。開啟后所有作用域下再執(zhí)行的JS代碼都按照嚴格模式處理
- 嚴格模式下,沒有寫執(zhí)行主體的話,this就是undefined;
arguments實參集合(只有傳遞了實參才會保持映射,不傳遞一直都是undefined)
當我們不知道用戶具體要傳遞幾個值得時候(傳遞幾個值都可以),此時我們無法設(shè)置形參的個數(shù);遇到此種情況,需要使用函數(shù)內(nèi)置的實參集合:arguments
1.arguments只有函數(shù)才有,是一個類數(shù)組集合
- 1、以數(shù)字作為索引(屬性名),從零開始
- 2、有一個length屬性,存儲的是當前實參的個數(shù) arguments.length
- 3、arguments.callee 動態(tài)的得到當前執(zhí)行函數(shù)的方法名
2.不管執(zhí)行函數(shù)是否傳遞參數(shù),arguments天生就存在,沒有傳遞參數(shù)時arg是個空的集合,傳遞了參數(shù)的arg中包含了所有傳遞的參數(shù)信息,
如果沒有傳遞實參,那么形參的值在arguments中就是undefined如果有變量名與形參沖突,那么操作的一直都是私有變量,argument中沒有傳遞實參的形參的值還是undefined
var a=12,b=13,c=14;
~function(a,b){
//b沒有傳遞實參所以在arguments中的值一直都是undefined
b=b||0;
arguments[0]=100;
var b=c=200;
console.log(a);
console.log(arguments[1]);//undefined
}(a);
console.log(a);
console.log(b);
console.log(c);
<font color=red>非嚴格模式下,如果傳遞的有實參,那么arguments的值永遠和對應(yīng)命名參數(shù)的值保持同步,如果沒有傳遞實參,則arguments中形參對應(yīng)的值就一直是undefined(來自高程3第66頁,在嚴格模式下arguments將不與實參保持映射關(guān)系。重寫arguments 的值會導致語法錯誤(代碼將不會執(zhí)行)),但是如果使用delete 刪除了arguments中某一個索引對應(yīng)的值,再去修改實參,那么實參和arguments將不再保持同步;<font>
(function (a) {
delete arguments[0];
a=1;
console.log(a);//1
console.log(arguments);
//[empty × 1, callee: ?, Symbol(Symbol.iterator): ?]
// callee:? (a)
// length:1
// Symbol(Symbol.iterator):? values()
// __proto__:Object
})(2);
JS中函數(shù)的返回值return
函數(shù)中是有返回值的,我們?nèi)绻朐谕饷媸褂煤瘮?shù)私有的一些信息,那么就需要通過return,把這些信息返回出來供外面使用。如果不寫return或者return后面什么都不寫,則默認返回undefined。函數(shù)體中,return后面的代碼不再執(zhí)行
return后面跟著的都是值:
function sum(){
var val=0;
return val;//返回的不是val變量,而是val所存儲的值
}
sum //代表函數(shù)本身
sum() //函數(shù)執(zhí)行,代表的是當前函數(shù)執(zhí)行后返回的結(jié)果(return后面是什么函數(shù)返回的就是什么)
JS中的匿名函數(shù)(沒有名字的函數(shù))
函數(shù)表達式—>把一個匿名函數(shù)(有名字也可以)作為值賦值給一個變量或者一個元素的某個事件)
- 聲明的變量指向函數(shù)體時(即函數(shù)表達式),變量的聲明會被提升(foo的聲明會被提升),但是它指向的函數(shù)體只會在執(zhí)行的時候才被賦值。對于 var bar = function foo(){};語句,其實就是一個有效的命名函數(shù)表達式,但有一點需要記住:命名函數(shù)表達式的標示符(即函數(shù)名稱)在外部作用域是無效的,在其內(nèi)部作用域是有效的。
自執(zhí)行函數(shù)—>創(chuàng)建函數(shù)和執(zhí)行函數(shù)放在一起了,創(chuàng)建完成后立馬執(zhí)行;(自執(zhí)行函數(shù)什么時候執(zhí)行?預解釋之后,JS代碼從上到下執(zhí)行,碰到自執(zhí)行函數(shù)時,才會創(chuàng)建和執(zhí)行;)
以下都是自執(zhí)行函數(shù),符號只是控制語法規(guī)范
除了第一種,其他4中方式都會改變自執(zhí)行函數(shù)的返回結(jié)果
- (function(){})();
- ~function(n){}(10); ~:按位非,執(zhí)行按位非的結(jié)果就是返回數(shù)值的反碼
- -function(n){}(10);
- +function(n){}(10);
- !function(n){}(10);
14、JS中操作DOM的屬性和方法
<font color=red>操作真實的DOM結(jié)構(gòu)是比較消耗性能的(盡量減少)</font>
DOM的映射機制
瀏覽器在渲染頁面的時候,給每一個元素都設(shè)置很多內(nèi)置的屬性(包含樣式的),當我們在JS中把堆內(nèi)存中的某些東西修改了,大部分情況下,瀏覽器都會檢測到你的修改,按照最新的修改的值重新渲染頁面中的元素。
DOM的重繪和回流
重繪(repaint)針對某一個元素
當元素的樣式發(fā)生改變(不修改元素位置的樣式),瀏覽器會把當前元素重新的進行渲染(DOM性能消耗低)
觸發(fā)重繪的場景:
- color的修改
- text-decoration的修改
- background的修改
- a:hover也會造成重繪
- :hover引起的顏色等不導致回流的style變動
回流(reflow)針對整個頁面
當元素的位置發(fā)生改變,瀏覽器會把整個頁面的DOM結(jié)構(gòu)進行重新計算,計算出所有元素的最新位置,然后再渲染(DOM性能消耗非常大)
- 1、width/height/border/margin/padding的修改
- 2、動畫,:hover等偽類引起的元素表現(xiàn)改動,display=none等造成頁面回流
- 3、appendChild等DOM元素操作
- 4、font類style的修改
- 5、background的修改,部分background的修改只觸發(fā)重繪,IE不用考慮
- 6、scroll頁面,不可避免
- 7、resize頁面,桌面版本進行瀏覽器大小的縮放。
- 8、讀取元素屬性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE));
如何解決重繪和回流
如何避免:
盡可能在 DOM 末梢通過改變 class 來修改元素的 style 屬性:盡可能的減少受影響的 DOM 元素。
避免設(shè)置多項內(nèi)聯(lián)樣式:使用常用的 class 的方式進行設(shè)置樣式,以避免設(shè)置樣式時訪問 DOM 的低效率。
設(shè)置動畫元素 position 屬性為 fixed 或者 absolute:由于當前元素從 DOM 流中獨立出來,因此受影響的只有當前元素,元素 repaint。
犧牲平滑度滿足性能:動畫精度太強,會造成更多次的 repaint/reflow,犧牲精度,能滿足性能的損耗,獲取性能和平滑度的平衡。
避免使用 table 進行布局:table 的每個元素的大小以及內(nèi)容的改動,都會導致整個 table 進行重新計算,造成大幅度的 repaint 或者 reflow。改用 div 則可以進行針對性的 repaint 和避免不必要的 reflow。
避免在 CSS 中使用運算式:學習 CSS 的時候就知道,這個應(yīng)該避免,不應(yīng)該加深到這一層再去了解,因為這個的后果確實非常嚴重,一旦存在動畫性的 repaint/reflow,那么每一幀動畫都會進行計算,性能消耗不容小覷。
獲取節(jié)點
<font color=red>document.getElementById()</font>
在文檔中通過元素的ID名來獲取一個元素,只能通過document對象來調(diào)用,document屬于Document類的一個實例,只有在Document類的原型上才有g(shù)etElementById這個方法。
我們把document稱之為上下文(context):上文和下文,也就是獲取元素時候限制的那個范圍。
獲取的結(jié)果是一個對象數(shù)據(jù)類型的值,在JS中使用DOM提供的方法,獲取的元素都是對象,所以我們把獲取的結(jié)果稱之為 ‘元素對象’。
1.如果頁面中的ID重復了,我們獲取的結(jié)果是ID對應(yīng)的第一個元素對象
2.在IE7及以下會把表單元素中的name屬性值當做ID來使用;而且會忽略ID的大小寫(項目中盡量不要讓表單的name和其他元素的id相同)
3.我們?nèi)绻袹S放在結(jié)構(gòu)下面,我們可以直接使用ID值來獲取這個元素(不需要通過getElementById來獲?。?,而這種方式會把頁面中所有ID為同一個的都獲取到(只有一個的話獲取的是元素對象,多個的話獲取的是類數(shù)組集合)=>不推薦
<font color=red>注意:不要讓表單元素的name屬性值和其他元素的id重復、不要用id的大小寫來區(qū)分不同的元素</font>
不通過直接操作ID值的方式如何獲取多個相同ID的元素?
var allList=document.getElementsByTagName('*');
var ary=[];
for(var i=0;i<allList.length;i++){
allList[i].id===xxx?ary.push(allList[i]):null
}
return ary;
<font color=red>context.getElementsByTagName():</font>
在指定上下文中通過元素的標簽名來獲取子子孫孫元素中的一組元素,獲取到的元素是類數(shù)組集合,通過索引來調(diào)用某一個
1、以數(shù)字作為屬性名,每一個屬性存儲的都是獲取到的每一個li,JS中我們把數(shù)字屬性名叫做“索引”(索引是逐級遞增的)
2、有一個length屬性存儲的是當前集合中LI的個數(shù)
具備以上的兩個特點特別像數(shù)組,但是,它不是數(shù)組,所以我們把他稱之為“類數(shù)組”
<font color=red>document.getElementsByName():
</font>在指定上下文中通過元素的name屬性來獲取一組元素(類數(shù)組:節(jié)點集合 NodeList)
在IE瀏覽器下只對表單元素起作用
這個方法應(yīng)用于獲取具有同樣name 的表單元素
<font color=red>context.getElementsByClassName():
</font>通過元素的樣式類名來獲?。╟lass值)一組元素,獲取的也是一個類數(shù)組集合,通過索引來調(diào)用某一個;
getElementsByClassName()是項目中最常用的一種方法,但是這個方法在IE6-8下不兼容
<font color=red>document.documentElement:</font>獲取HTML元素
<font color=red>document.body:</font>獲取body元素
在移動端獲取元素常用的方法(IE6-8下不兼容)
documetn.querySelector():
獲取一個
document.querySelectorAll():
獲取多個 類數(shù)組集合(獲取到的元素集合或節(jié)點集合不存在DOM映射,因為獲取到的集合不是標準的NodeList,而是屬于StaticNodeList(靜態(tài)集合))
document.querySelector("#tab");ID選擇器
document.querySelectorAll("#tab li ");//層級選擇器
document.querySelectorAll("input[type='radio']");//過慮選擇器
className:
通過這個屬性可以獲取或者設(shè)置當前元素對象的樣式類
innerHTML:
通過這個屬性可以獲取或者設(shè)置元素里面的內(nèi)容
innerText:
通過這個屬性可以設(shè)置或者獲取元素里面的文本內(nèi)容
火狐中不支持innerText屬性,我們用textContent代替innerText
<font color=red>innerHTML與innerText的區(qū)別:</font>
- innerHTML可以把增加內(nèi)容中的HTML標簽進行識別,innerText只能設(shè)置或者獲取文本內(nèi)容,不能識別HTML標簽,HTML標簽會被當作文本處理
style屬性:
通過這個屬性我們可以獲取或者設(shè)置元素的樣式(只能是元素的行內(nèi)樣式,獲取或者設(shè)置都是,寫在內(nèi)嵌或者外鏈中的css樣式無法獲取到)
window.getComputStyle(元素,偽類)[屬性值] || 元素.currentStyle[屬性值]:
獲取當前元素經(jīng)過瀏覽器計算的屬性值,不管是寫在哪的;
獲取關(guān)系的屬性
childNodes:獲取所有的子節(jié)點
獲取當前元素的所有子節(jié)點(節(jié)點集合:類數(shù)組)
注:不僅是元素子節(jié)點,文本、注釋等都會包含在內(nèi);子節(jié)點說明只是在兒子輩分中查找;
childredn:獲取所有的元素子節(jié)點
獲取所有的元素子節(jié)點(元素集合)
在IE6-8下獲取的結(jié)果和標準瀏覽器中有區(qū)別:會把注釋節(jié)點當作元素節(jié)點獲取到
parentNode:獲取元素的父親節(jié)點(元素對象)
previousSibling:獲取哥哥節(jié)點(包括文本或注釋)
nextSibling:獲取弟弟節(jié)點(包括文本或注釋)
IE6-8下不兼容
previousElementSibling:獲取上一個元素節(jié)點
nextElementSibling:獲取下一個元素節(jié)點
firstChild:獲取所有子節(jié)點中的第一個
lastChild:獲取所有子節(jié)點的最后一個
firstElementChild/lastElementChild IE6-8下不兼容
節(jié)點類型
節(jié)點類型 nodeType nodeName nodeValue 元素節(jié)點 1 大寫的標簽名 null 屬性節(jié)點 2 大寫的屬性名 屬性值 文本節(jié)點 3 #text 文字內(nèi)容 注釋節(jié)點 8 #comment 注釋內(nèi)容 document 9 #document null 在標準瀏覽器下,我們把空格和回車都當成文本節(jié)點處理
ele.tagName:獲取當前元素的標簽名(獲取的一般都是大寫),tagName只有元素節(jié)點才有;
節(jié)點操作
創(chuàng)建節(jié)點
document.createElement(元素標簽):動態(tài)創(chuàng)建一個元素節(jié)點
document.createAttribute(元素屬性):動態(tài)創(chuàng)建一個屬性節(jié)點
document.createTextNode(文本內(nèi)容):動態(tài)創(chuàng)建一個文本節(jié)點
插入節(jié)點
appendChild():把元素添加到指定容器的末尾位置
容器.appendChild(元素)
insertBefore(a,b):把新的元素a,添加到老的元素b之前
容器.insertBefore(a,b)
替換節(jié)點
replaceChild(a,b):用a元素來替換容器中的b元素
容器.replcaeChild(a,b);
復制節(jié)點
cloneNode(true/false):克隆元素節(jié)點,true表示包括所有子節(jié)點,默認為false
要克隆的元素.cloneNode(),無法克隆元素綁定的事件
刪除節(jié)點
removerChild():從容器中刪除指定元素
容器.removeChild(元素)
屬性操作
獲取屬性
getAttribute(元素屬性名):獲取元素節(jié)點中指定屬性的屬性值
元素節(jié)點.getAttribute(元素屬性名)
設(shè)置屬性
setAttribute(元素屬性名):設(shè)置或者改變元素節(jié)點的屬性值(一般都是操作自定義屬性)
元素節(jié)點.setAttribute(屬性名,屬性值),可以設(shè)置自定義的屬性,會修改html結(jié)構(gòu),直接在html中體現(xiàn)出來。用setAttribute設(shè)置的只能用getAttribute來獲取,只能用removeAttribute來刪除
注意:在IE6-8下無法修改和設(shè)置class屬性
刪除屬性
removeAttribute(元素屬性名):刪除元素節(jié)點的屬性
元素節(jié)點.removeAttribute(屬性名)
設(shè)置或者獲取自定義屬性有xxx.屬性名=屬性值和 xxx.setAttribute(元素屬性名)兩種方式,有以下區(qū)別:
1,xxx.屬性名 結(jié)果體現(xiàn)在對象的屬性上。與HTML結(jié)構(gòu)無關(guān)。
1,xxx.setAttribute(屬性名) 結(jié)果體現(xiàn)在DOM結(jié)構(gòu)上,即在HTML結(jié)構(gòu)中可以看到;
如果使用DOM的內(nèi)置屬性操作元素,那么元素就會被當作特殊對象,和HTML結(jié)構(gòu)產(chǎn)生映射關(guān)系(即結(jié)果會呈現(xiàn)在HTML結(jié)構(gòu)上)