本系列將從如下專題去總結:javascript
1. JS基礎知識深刻總結
2. 對象高級
3. 函數高級
4. 事件對象與事件機制前端
暫時會對以上四個專題去總結,如今開始JS之旅的第一部分:JS基礎知識深刻總結。下圖是我這篇的大綱。 java
話在前面:我一直都認爲,在互聯網學習的大環境下,網路學習資源不少,但這些博客等僅僅是一個嚮導,一個跳板,真正學習知識仍是須要線下,須要書籍。 另外,若有錯誤,請留言或私信。一塊兒成長,謝謝。git
基本類型 | 類型的值 | 檢測方法 |
---|---|---|
Number | 能夠任意數值 | 用typeof檢測結果爲number |
String | 能夠任意字符串 | 用typeof檢測結果爲string |
Boolean | 只有true/false | 用typeof檢測結果爲boolean |
undefined | 只有undefined | 用typeof檢測數據類型和‘===’(全等符號) |
null | 只有null | ‘===’(全等符號) |
Symbol | 經過Symbol()獲得,值可任意 | 用typeof可檢測結果爲symbol |
對象類型 | 描述 | 檢測方法 |
---|---|---|
Object | 能夠任意對象 | 能夠用typeof/instanceof檢測數據類型 |
Array | 一種特別的對象(有數值下標,並且內部數據是有序的。通常的對象內部的數據是無序的,好比你一個對象中有name和age,他們是無序的。) | instanceof |
Function | 一種特別的對象(能夠去執行的對象,內部包含可運行的代碼。一個普通的對象能夠執行嗎?不能。)另外,對象是存儲一些數據的,固然函數也是存儲一些代碼數據。 | typeof |
Date | 時間對象 | instanceof |
RegExp | 正則對象 | instanceof |
基本數據類型 | 對象類型 |
---|---|
基本類型的值是不可變的 | 引用類型的值是可變的 |
基本類型的比較是它們的值的比較 | 引用類型的比較是引用(指針指向)的比較 |
基本類型的變量是存放在棧內存(Stack)裏的 | 引用類型的值是保存在堆內存(Heap)中的對象(Object) |
typeof 注1:用typeof
判斷返回數據類型的字符串(小寫)表達。好比:typeof ‘hello’
結果是 string
。 注2:用typeof
來測試有如下七種輸出結果:number
string
boolean
object
function
symol
undefined
。 所以typeof不能去判斷出null
與object
,由於用typeof
去判斷null
會輸出object
。 注3:全部的任何對象,用typeof
測試數據類型都是object
。所以,typeof
不能去判斷出object
與array
。github
===(全等符號) 注1:只能夠判斷undefined 和 null 由於這兩種基本類型的值是惟一的,便可用全等符比較。面試
instanceof 注1:A instanceof B
翻譯就是B的實例對象是A 嗎? 判斷對象的具體類型(究竟是對象類型中的Object
Array
Function
Date
RegExp
的哪個具體的類型),返回一個Boolean值。ajax
借調法:Object.prototype.toString.call()
注1:這種方法只能夠檢測出內置類型(引擎定義好的,自定義的不行),這種方法是相對而言更加安全。Object
Date
String
Number
RegExp
Boolean
Array
Math
Window
等這些內置類型。算法
以上說明都有案例在面試題裏json
var obj={ name:'lvya' };
console.log(obj.age); //undefined
console.log(age); //報錯,age is not defined
複製代碼
從這個點再去看一個簡單的例子:function Person(name,age,price) {
this.name = name
this.age = age
this.price=price
setName=function (name) {
this.name=name;
}
}
var p1 = new Person('LV',18,'10w')
console.log(p1.price); // 10w
複製代碼
根據上面這個例子,問個問題。請問訪問p1.price
先找啥?後找啥?經過啥來找?(問題問的很差,直接看答案吧) An:p1.price
先找p1
後找 price
。 p1是一個全局變量哦,這個全局變量自己存在棧內存中,它的值是一個地址值,指向new Person
出來的對象。怎麼找呢?先找p1是沿着做用域找的,後找price是沿着原型鏈找的。這就是聯繫,從另一個方面細看問題。可能這樣看問題,你就能夠把原型鏈和做用域能夠聯繫起來思考其餘問題。串聯知識點:請你講講什麼是原型鏈和做用域鏈? 咱們從a.b這個簡單的表達式就能夠看出原型鏈和做用域鏈。(a正如上例的p1)第一步先找a!a是一個變量,經過做用域鏈去查找,一層一層往外找,一直找到最外層的window,還沒找到那就會報錯,
a is not defined
。 找到a這個變量,它的值有兩種狀況:基本數據類型和對象類型。 若是是基本數據類型(除了undefined和null)會使用包裝類,生成屬性。若是是undefined和null就會報錯,顯示不能讀一個undefined或null的屬性。 若是是對象類型,這就是對象.屬性的方式,開始在對象自身查找,找不到沿着原型鏈去找。原型鏈也找不到的時候,那麼就會輸出undefined
。數組
問題2:undefined與null的區別? undefined
表明定義未賦值 nulll
定義並賦值了, 只是值爲null
var a;
console.log(a); // undefined
a = null;
console.log(a); // null
複製代碼
使用Object.prototype.toString.call()
形式能夠具體打印類型來區別undefined和null。 若是值是undefined
,返回「[object Undefined]」。 若是這個值爲null
,則返回「[object Null]」。
問題3:何時給變量賦值爲null 呢? 初始賦值, 代表這個變量我將要去賦值爲對象 結束前, 這個對象再也不使用時,讓對象成爲垃圾對象(被垃圾回收器回收)
//起始
var b = null // 初始賦值爲null, 代表變量b將要賦值爲對象類型
//肯定賦值爲對象
b = ['lvya', 12]
//結束,當這個變量用不到時
b = null // 讓b指向的對象成爲垃圾對象(被垃圾回收器回收)
// b = 2 //固然讓b=2也能夠,但不常使用
複製代碼
問題4:變量類型與數據類型同樣嗎? 數據的類型:包含基本數據類型和對象類型 變量的類型(實則是變量內存值的類型) JS弱類型語言,變量自己是無類型的。包含基本類型
: 保存的就是基本類型的數據(好比:數字1,字符串‘hello lvya’,布爾值false等)和引用類型
: 保存的是地址值,這個地址值去指向某個對象。
toString()
和valueOf()
都是在Object.prototype
裏面定義.
toString()
表示的含義是把這個對象表示成字符串形式, 而且返回這個字符串形式. 首先,在Object.prototype
中它對toString()方法的默認實現是"[object Object]"。 驗證一下:
var p={};
console.log(p.toString()); //[object Object] 去Object.prototype的去找(輸出他的默認實現)
function Person(){
}
var p1=new Person();
console.log(p1.toString()); //[object Object] 去Object.prototype的去找(輸出他的默認實現)
複製代碼
再看一下能夠在本身的對象或者原型上對 toString() 進行覆寫(重寫, override)。這時訪問這個對象的toString()方法時,就會沿着原型鏈上查找,恰好在自身對象上就找到了toString(),這個時候就再也不去找原型鏈上的頂端Object.prototype
的默認的toString()啦,便實現了對象的toString()的重寫。 驗證一下:
var p = {
toString: function (){
return "100";
}
};
//100 這個時候就會在首先在P對象上找toString()方法,這個時候就是對toString方法的重寫
console.log(p.toString());
複製代碼
再舉一個重寫的栗子:
var date = new Date();
console.log(date.toString());
//Fri Jan 18 2019 21:13:44 GMT+0800 (中國標準時間)
/*從輸出結果可知,Date這個構造函數的原型實際上是有toString()方法的, 說明JS引擎已經在Date原型對象中重寫了toString()方法, 故不會在Object.prototype中找*/
console.log(Date.prototype); //發現確實有toString()方法
var n = new Number(1);
console.log(n.toString()); //1(字符串)
/* 同理:這就是說明他們在js引擎內置的包裝對象,說白了,就是內部已經給Number對象上重寫了 toString()方法。這個方法恰好就是將數字轉爲字符串*/
複製代碼
valueOf()
應該返回這個對象表示的基本類型的值!在Object.prototype.valueOf
中找到, 默認返回的是this。當須要在對象上重寫valueOf()
時,應該是返回一個基本數據類型的值。 先看一個默認返回的值的狀況。(也就是說它是去這個對象的原型鏈的頂端Object.prototype.valueOf
找的valueOf
方法 )
function Person(){
}
var p1 = new Person();
console.log(p1.valueOf() == p1); //true
複製代碼
對返回結果的說明:這個時候p1.valueOf
是在Object.prototype.valueOf
找到的,返回值默認this。此時this就是p1的這個對象。故結果返回true
。 如今看一下重寫valueOf後的狀況
var p = {
toString: function (){
return "100";
},
valueOf : function (){
return 1;
}
};
console.log(p.toString()); //100(字符串)
//還來不及去Object.prototype.valueOf 其自己就有了toString方法 故固然讀自己對象的toString()方法
console.log(p.valueOf()); //1(number數據類型)
//同理,沒去Object.prototype.valueOf找 而是找其自己的valueOf方法
複製代碼
咱們再來驗證JS引擎對那些內置對象有去重寫toString()
和valueOf()
呢?
var n = new Number(100);
console.log(n.valueOf()); //100 (number類型)
var s = new String("abc");
console.log(s.valueOf()); //abc (string類型)
var regExp = /abc/gi;
console.log(regExp.valueOf() === regExp); //true
//說明這個時候正則對象上沒有valueOf,是在Object.prototype.valueOf找的,返回this,this指的就是regExp正則對象。
複製代碼
結論:在JS中, 只有基本類型中那幾個包裝類型進行了重寫, 返回的是具體的基本類型的值, 其餘的類型都沒有重寫,是去對象原型鏈的頂層Object.prototype.valueOf
去找的。
瞭解完valueOf()
和toSting()
方法後,其實他們就是對象與基本數據類型的比較的基礎。咱們數據類型,分爲基本數據類型和對象類型兩種,故在數據類型比較中,只會有三種狀況:
基本數據類型間的比較
規則:若是類型相同,則直接比較; 若是類型不一樣, 都去轉成
number
類型再去比較 三個特殊點:1.undefined
==null
2.0
和undefined
,0
和null
都不等 3. 若是有兩個NaN
參與比較,則老是不等的。
總結:都是基本數據類型,但當類型不一樣時,轉爲number類型的規律以下:
基本類型中非number類型 | 轉爲number類型 |
---|---|
undefined ‘12a’ ‘abc’ ‘\’ |
Nan |
'' ' ' '\t' '0' null false |
0 |
true ‘1’ |
1 |
‘12’ |
12 |
咱們來看看ECMA官方文檔對轉number類型的說明:
另外 再補充一點,在JS世界裏,只有五種轉Boolean類型是false
的:
0
Nan
undefined
null
""
false
。其餘的轉Boolean值都是
true
。 咱們再來看看ECMA官方文檔對轉Boolean類型的說明:
因此,從這裏咱們就能夠發現其實原文的ECMA官方文檔就是很棒的學習資料,已經幫你整理的很完備了。多去翻翻這些官方文檔的資料頗有幫助。
例子1:屬於基本類型間的比較,並且都是基本類型中的number類型,相同類型直接比較。
var a=1;
var b=1;
console.log(a == b); //true
console.log("0" == ""); //false
//都是相同的string類型,不用轉,直接用字符串比較
複製代碼
例子2:屬於基本類型間的比較,可是其具體的類型不一樣,須要轉爲number
類型再去比較。
console.log(true == "true"); //false 相應轉爲number類型去比較:1與Nan比較
console.log(0 == "0"); //true 相應轉爲number類型去比較:0與0比較
console.log(0 == ""); //true 相應轉爲number類型去比較:0與0比較
console.log(undefined == null); //true Nan與0比較??特殊
複製代碼
例子3:屬於三大特殊點
console.log(undefined == null); //true
console.log(undefined == 0); //false
console.log(null == 0); //false
console.log(Nan == Nan); //false
複製代碼
對象類型間的比較
對象間的比較中
===
(嚴格相等:值和類型都相等) 和==
徹底同樣。 規則:其實比較是否是同一個對象,比的就是他們的地址值是否同樣。
例子1:對象類型間的比較
console.log({} === {}); //false 地址值不一樣
console.log(new Number(1) == new Number(1)); //false 地址值不一樣
複製代碼
基本類型與對象類型間的比較
重點:這就是爲啥以前引入
valueOf
和toString()
的道理。 規則:把對象轉成基本類型的數據以後再比 ?如何把對象轉換成基本類型:1. 先調用這個對象(注意是對象)的valueOf()
方法, 若是這個方法返回的是一個基本類型的值, 則用這個基本類型去參與比較。 2. 若是valueOf()
返回的不是基本類型, 則去調用toString()
而後用返回的字符串去參與比較。這個時候就是字符串與那個基本類型的比較,問題從而轉爲了基本類型間的比較。
例子1:
var p = {};
console.log(p.valueOf()); //{}
console.log(p == "[object Object]"); //true
複製代碼
解釋:首先明確是對象與基本類型中的字符串比較;按照規則,先把對象調用其valueOf()
,根據上節知識可知,返回的是this,也就是當前對象{}。不是基本數據類型,故再調用其toString()
,返回"[object Object]"
,從而進行基本數據類型間的比較,根據規則,類型相同都是字符串,直接比較,故相等。
例子2:
var p1 = {
valueOf : function (){
return 'abc';
},
toString : function (){
return {};
}
}
console.log(p1 == "abc"); //true
複製代碼
解釋:首先明確是對象與基本類型中的字符串比較;按照規則,先把對象調用其valueOf()
,根據上節知識可知,p有重寫 valueOf
,故直接輸出字符串'abc'
,它屬於基本數據類型,故再也不調用其toString()
。進而進行基本數據類型間的比較,根據規則,類型相同都是字符串'abc'
,直接比較,故相等。
案例1: 基本數據類型的判斷
typeof
返回數據類型的字符串(小寫)表達
var a;
console.log(a, typeof a, typeof a === 'undefined', a === undefined) // undefined 'undefined' true true
console.log(undefined === 'undefined'); //false(轉爲number實則是Nan與0的比較)
a = 4;
console.log(typeof a === 'number'); //true
a = 'lvya';
console.log(typeof a === 'string'); //true
a = true;
console.log(typeof a === 'boolean'); //true
a = null;
console.log(typeof a, a === null); // 'object' true
複製代碼
案例2: 對象類型的判斷
var b1 = {
b2: [1, 'abc', console.log],
b3: function () {
console.log('b3');
return function () {
return 'ya Lv'
}
}
};
console.log(b1 instanceof Object, b1 instanceof Array); // true false
console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) ;// true true
console.log(b1.b3 instanceof Function, b1.b3 instanceof Object); // true true
console.log(typeof b1.b2); // 'object'
console.log(typeof b1.b3 === 'function');// true
console.log(typeof b1.b2[2] === 'function'); // true
b1.b2[2](4); //4
console.log(b1.b3()()); //ya Lv
複製代碼
instanceof
通常測對象類型,那它去測基本數據類型會出現怎樣的奇妙火花呢?一塊兒來驗證一下。instanceOf
內部的實現原理能夠直接看3.2.3節。
//1並非Number類型的實例
console.log(1 instanceof Number); //false
//new Number(1)的確是Number類型的實例
console.log(new Number(1) instanceof Number); //true
複製代碼
面試3: 考察typeOf
檢測數據類型
用
typeof
來測試有如下七種輸出結果:'number'
'string'
'boolean'
'object'
'function'
'symol'
'undefined'
。注意都是字符串表達方式。
console.log(typeof "ab"); // string
console.log(String("ab")); //'ab' 能夠知道String("ab")就是var s='ab'的含義
console.log(typeof String("ab")); // string
console.log(typeof new String("ab")); // object
console.log(typeof /a/gi); // object
console.log(typeof [0,'abc']); // object
console.log(typeof function (){}); //function
var f = new Function("console.log('abc')");
f(); //'abc' 能夠知道f就是一個函數
console.log(typeof f); //function
console.log(typeof new Function("var a = 10")); //function
複製代碼
面試4: 考察+
的運用
JS加號有兩種用法: Case 1:數學上的加法(只要沒有字符串參與運算就必定是數學上的數字): Case 2:字符串鏈接符(只要有一個是字符串,那就是字符串連接)
console.log(1 + "2" + "2"); // 122
console.log(1 + +"2" + "2"); // 32 (這裏+'2'前面的加號是強轉爲number的意思)
console.log(1 + -"1" + "2"); // 02
console.log(+"1" + "1" + "2"); // 112
console.log( "A" - "B" + "2"); // NaN2
console.log( "A" - "B" + 2); // NaN
複製代碼
面試5: 考察valueOf
和toString
console.log([] == ![]); //true
複製代碼
說明:首先左邊是[]
,右邊是![]
這個是一個總體,由1.1.6節知識可知,世界上只有五種轉Boolean值得是false
,其餘都是true
。故右邊這個![]
總體結果是false
。綜上,明確這是對象與基本類型(布爾值)的比較。 而後,就是將對象先調用valueOf
後調用toString
的規則去判斷,由1.1.6節可知,左邊是對象,首先用valueOf
返回的是一個數組對象(注意若是是{}
。valueOf()
就是返回this
,此時this
是{}
!) 而後再調用toString
返回一個空的字符串,由於數組轉字符串,就是去掉左右「中括號」,把值和逗號轉爲字符串,看一下驗證:
console.log([].valueOf()); //[]
console.log([].valueOf().toString()); //空的字符串
複製代碼
故左邊是一個空的字符串。右邊是false
。又轉爲基本數據間的比較,兩個不一樣類型,則轉爲number
類型去比較。 空字符串轉number
爲0,false
轉number
爲0。故0==0
結果就是true
。
面試6: &&
||
的短路現象
&&
||
在js中一個特別靈活的用法。若是第一個能最終決定結果的,那麼結果就是第一個值,不然就是第二個。這個在實際項目中使用也很常見。 與和或的優先級:"與" 高於 "或",也就是&&
優先級大於||
console.log(1 && 2 || 0); // 2
console.log((0 || 2 && 1)); //1 (注意,這裏是先計算2 && 1,由於&&優先級高於||)
console.log(3 || 2 && 1); // 1 (注意,這裏是先計算2 && 1,由於&&優先級高於||)
console.log(0 && 2 || 1); // 1
複製代碼
面試7: 類型轉換綜合題
+當作數字相加,由於兩邊都沒字符串,故都轉number
var bar = true;
console.log(bar + 0); // 1
複製代碼
var bar = true;
console.log(bar + true); // 2
複製代碼
var bar = true;
console.log(bar + false); // 1
複製代碼
var bar = true;
console.log(bar + undefined); // Nan
複製代碼
var bar = true;
console.log(bar + null); // 1
複製代碼
console.log(undefined + null); // Nan (Nan與任何運算結果都是Nan)
複製代碼
+當作字符串鏈接,由於有一個爲字符串
var bar = true;
console.log(bar + "xyz"); // truexyz
複製代碼
隱含的類型轉換
console.log([1, 2] + {}); //1,2[object Object]
複製代碼
Array.prototype.valueOf = function () {
return this[0];
};
console.log([1, 2] + [2]); //3
/**重寫了Array的valueOf方法,其重寫後返回的是this[0], 由於在這是number類型1,故直接用。*/
複製代碼
console.log([{}, 2] + [2]); // [object Object],22
/**重寫了Array的valueOf方法,其重寫後返回的是this[0], 由於在這是一個對象{},故從新在對這個數組對象([{},2])調用toString()返回‘[object Object],2’。 這裏要注意當調用toString是整個對象,而非重寫valueOf後返回來的對象。 +右邊的[2]是調用了valueOf以後返回的number類型2,因此直接用, 由於左邊是一個字符串,因此加號表明字符串拼接。返回最終結果[object Object],22 */
複製代碼
存儲在內存中特定信息的"東東",本質上是0101...的二進制
數據的特色:可傳遞, 可運算
var a = 3;
var b = a;
複製代碼
這裏體現的就是數據的傳遞性。變量a是基本數據類型,保存的值是基本數據類型number值爲3。在棧內存中存儲。這兩個語句傳遞的是變量a嗎?不是。傳遞的是數據3。實際上,是拿到變量a的內容數字3拷貝一份到b的內存空間中。
注意:無論在棧內存空間保存的基本數據類型仍是在堆內存中保存的對象類型,這些內存都有地址值。只是要不要用這個地址值的問題。對象的地址值通常會用到。因此不少人會誤覺得只有對象纔有地址值,這是錯誤的理解。
內存條通電後產生的可存儲數據的空間(臨時的),它是臨時的,但處理數據快
硬盤的數據是永久的,但其處理數據慢
內存產生和死亡: 內存條(電路版) -> 通電 -> 產生內存空間 -> 存儲數據 -> 處理數據 -> 斷電 -> 內存空間和數據都消失
內存空間的分類:
棧空間: 全局變量和局部變量【空間比較小】
堆空間: 對象 (指的是對象(函數也是對象)自己在堆空間裏,其自己在堆內存中。但函數名在棧空間裏。)【空間比較大】
//obj這個變量在棧空間裏 name是在堆空間裏
function fn () {
var obj = {name: 'lvya'}
}
複製代碼
一塊小的內存包含2個方面的數據
內部存儲的數據(內容數據)
地址值數據(只有一種狀況讀的是地址值數據,那就是將一個對象給一個變量時)
var obj = {name: 'lvya'} ;
var a = obj ;
console.log(obj.name) ;
複製代碼
執行var obj = {name: 'Tom'}
是將右邊的這個對象的地址值給變量obj,變量obj這個內存裏面保存的就是這個對象的地址值。 而var a = obj
右邊不是一個對象,是一個變量(引用類型的變量),把obj的內容拷貝給a,而恰好obj的存儲的內容是一個對象的地址值。 執行console.log(obj.name)
讀的是obj.name的內容值。 總結:何時讀的是地址值?只有把一個對象賦值給一個變量時纔會讀取這個對象在內存塊中的地址值數據。上述三條語句只有var obj = {name: 'Tom'}
纔是屬於讀地址值的狀況。
問題一:var a = xxx, a內存中到底保存的是什麼?
須要分類討論:
當xxx是基本數據, 保存的就是這個數據
var a = 3;
//3是基本數據類型,變量a保存的就是3.
複製代碼
當xxx是對象, 保存的是對象的地址值
a = function () {
}
//函數是對象,那麼a保存的就是這個函數對象的地址值。
複製代碼
當xxx是一個變量, 保存的xxx的內存內容(這個內容多是基本數據, 也多是地址值)
var b = 'abc'
a = b
//b是一個變量,而b自己內存中的內容是一個基本數據類型。
//因此,a也是保存這個基本數據類型'abc'
複製代碼
b = {}
a = b
//b是一個變量,而b自己內存中的內容是一個對象的地址值。
//因此,a也是保存這個對象的地址值'0x123'
複製代碼
問題二:關於引用變量賦值問題?
2個引用變量指向同一個對象, 經過一個變量修改對象內部數據, 另外一個變量看到的是修改以後的數據
var obj1 = {name: 'Tom'}
var obj2 = obj1
obj2.name = 'Git'
console.log(obj1.name) // 'Git'
function fn (obj) {
obj.name = 'A'
}
fn(obj1)
console.log(obj2.name) //A
複製代碼
執行var obj2 = obj1
obj1
是一個變量,而非對象。故把obj1的內容拷貝給obj2,只是恰好這個內容是一個對象的地址值。這個時候,obj1
obj2
這兩個引用變量指向同一個對象{name: 'Tom'}
。 經過其中一個變量obj2
修改對象內部的數據。 obj2.name = 'Git'
那麼這個時候,另一個對象看到的是修改後的結果。固然,後面的對fn(obj1)
,也是同樣的操做,涉及到實參和形參的賦值。
2個引用變量指向同一個對象, 讓其中一個引用變量指向另外一個對象, 另外一引用變量依然指向前一個對象。
var a = {age: 12};
var b = a;
a = {name: 'BOB', age: 13};
b.age = 14;
console.log(a.name, a.age,b.age);// 'BOB' 13 14
function fn2 (obj) {
obj = {age: 100}
console.log(obj.age); //100
}
fn2(a); //函數執行完後會釋放其局部變量
console.log(a.age,b.age) //13 14
console.log(obj.age); //報錯 obj is not defined
複製代碼
一開始兩個引用變量a
b
都指向同一個對象,然後執行a = {name: 'BOB', age: 13};
語句,就是讓a
指向另外一個對象{name: 'BOB', age: 13}
,a
中的內容的地址值變化了。而b
仍是指向以前a
的那個對象{age: 12}
。 這個例子要好好理解,看圖解:未執行fn2函數以前和執行fn2函數後
問題三:在JS調用函數時傳遞變量參數時,是值傳遞仍是引用傳遞?
理解1: 都是值(基本類型的值/對象的地址值)傳遞
理解2: 多是值傳遞, 也多是引用傳遞(這個時候引用傳遞理解爲對象的地址值)
var a = 3
function fn (a) {
a = a +1
}
fn(a)
console.log(a) //3
複製代碼
fn(a)
中的a
是一個實參。function fn (a)
中的a
是一個形參。 var a = 3
中的a
是一個全局變量,其內存中存儲的內容是基本數據類型3。實參與形參的傳遞是把3傳遞(拷貝)給形參中的a的內存中的內容。傳遞的不是a!而是3。而後,執行完以後,函數裏面的局部變量a
被釋放,當輸出a
值時,確定去讀取全局變量的a
。傳遞的是值3。
function fn2 (obj) {
console.log(obj.name) //'Tom'
}
var obj = {name: 'Tom'}
fn2(obj)
複製代碼
fn2(obj)
中的實參obj
(引用變量)把其內容(恰好是地址值)傳遞給形參中的內容。而不是指把{name: 'Tom'}
整個對象賦值給形參obj
。是把地址值拷貝給形參obj
。也就是實參obj
形參obj
這兩個引用變量的內容同樣(地址值同樣)。傳遞的是地址值。
問題四:JS引擎如何管理內存?
內存生命週期 分配小內存空間, 獲得它的使用權 存儲數據, 能夠反覆進行操做 釋放小內存空間
釋放內存 局部變量:函數執行完自動釋放(全局變量不會釋放內存空間) 對象:成爲垃圾對象==>垃圾回收器回收(會有短暫間隔)
var a = 3
var obj = {}
//這個時候有三塊小的內存空間:第一塊 var a = 3 第二塊 var obj 第三塊 {} 在堆內存中
obj = undefined
//無論obj = null/undefined 這個時候內存空間還有兩塊。沒有用到的{}會有垃圾回收器回收。
function fn () {
var b = {}
//b局部變量 整個局部變量的生命週期是在函數開始執行到結束。
}
fn() // 局部變量b是自動釋放, b所指向的對象是在後面的某個時刻由垃圾回收器回收
複製代碼
這一節主要是對對象的基本理解和使用做出闡述,一些基本的問題筆者會簡單地在Part 1這部分羅列出來。具體的深刻問題在Part 2中深刻探討。那麼在瞭解對象的概念時,思想很重要,那就是對象是如何產生的?對象內部有啥須要關注?至於對象如何產出,有new出來,字面量定義出來的,函數return出來的等等狀況。至於內部有啥呢,主要關注就是屬性和方法這兩個東西。
什麼是對象?
爲何要用對象? 統一管理多個數據。若是不這麼作,那麼就要引入不少的變量。 好比我如今要創建一個對象Person
,裏面有name
age
gender
等等,我能夠用一個對象去創建數據容器,就不須要單獨設置不少變量了。方便管理。
對象的分類?
Math
/String
/Function
/Number
/Data
console.log()
document.write()
對象的組成?
在瞭解完對象以後,咱們知道每一個對象會去封裝一些數據,用這個對象去映射某個事物。那麼這些數據就是由屬性來組成的,如今咱們看看屬性的一些相關知識。
屬性組成?
屬性名 : 字符串(標識),本質上是字符串。本質上屬性名是加引號的,也就是字符串。但通常實際操做都不加。
屬性值 : 任意類型(因此會有方法是特別的屬性這一說法。)
屬性名本質上是字符串類型,見下例:
var obj={
'name':'豬八戒';
'gender':'男';
}
複製代碼
上例子通常咱們不會特地這樣去將屬性名打上雙引號,咱們通常習慣這樣寫:
var obj={
name:'豬八戒';
gender:'男';
}
複製代碼
再看一道對象屬性名知識點的面試題:
var a = {},
b = {key: 'b'},
c = {key: 'c'};
a[b] = 123; // a["[object Object]"] = 123
a[c] = 456; // a["[object Object]"] =456
console.log(a[b]); //輸出456 求a["[object Object]"]=?
複製代碼
上例解釋:屬性名本質上是字符串。ES6以前對象的屬性名只能是字符串, 不能是其餘類型的數據! 若是你傳入的是其餘類型的數據做爲屬性名, 則會把其餘類型的數據轉換成字符串,再作屬性名。如果對象,那麼就調用toString()
,ES6 屬性名能夠是Symbol
類型。 再看一個例子:
var a = {
"0" : "A",
"1" : "B",
length : 2
};
for(var i = 0; i < a.length; i++){//a是對象,a.length是讀取對象的屬性,爲2.
console.log(a[i]);
}
//會輸出A B
複製代碼
再看一個例子:
var a = {};
a[[10,20]] = 2000;
//首先把握好a是對象,a[]就是使用對象讀其屬性的語法,而不是數組。把a[]中[10,20]本質上是字符串,因此要轉啊。
//[10,20]轉字符串就是對象轉字符串,調用toString(),變成「10,20」。這個轉的字符串就是屬性名。
console.log(a); // 輸出 {10,20: 2000}
複製代碼
屬性的分類?
數組和函數是特別的對象?
如何訪問對象內部的數據 ?
.屬性名
: 編碼簡單, 有時不能用。['屬性名']
:編碼麻煩, 能通用。var p = {
name: 'Tom',
age: 12,
setName: function (name) {
this.name = name
},
setAge: function (age) {
this.age = age
}
};
p.setName('Bob') //用.屬性名的方式
p['setAge'](23) //用['屬性名']語法
console.log(p.name, p['age']) //Bob 23
複製代碼
何時必須使用['屬性名']
的方式?
var p = {};
//1. 給p對象添加一個屬性: content type: text/json
// p.content-type = 'text/json' //不能用
p['content-type'] = 'text/json'
console.log(p['content-type'])
//2. 屬性名不肯定,用變量去存儲這個值。
var propName = 'myAge'
var value = 18
// p.propName = value //不能用
p[propName] = value //propName表明着的就是一個變量
console.log(p[propName]) //18
複製代碼
函數對象(Function Object)是什麼呢?
其實在JavaScript中筆者認爲最複雜的數據類型,不是對象,而是函數。爲何函數是最複雜的數據類型呢?由於函數能夠是對象,它自己就會有對象的複雜度。函數又能夠執行,它有不少的執行調用的方式(這也決定了函數中this
是誰的問題),因此他又有函數執行的複雜度。這一小節咱們就簡單來講說函數的基本知識。在Part3會去更深刻去介紹JS中的函數。
什麼是函數?
爲何要用函數?
提升複用性(封裝代碼)
便於閱讀交流
function showInfo (age) {
if(age<18) {
console.log('未成年, 再等等!')
} else if(age>60) {
console.log('算了吧!')
} else {
console.log('恰好!')
}
}
//若是不用函數作,也能夠,但要把中間的代碼書寫不少遍。
//而函數就是抽象出共同的東西,把這些執行過程封裝起來,給你們一塊兒用。
showInfo(17) //未成年, 再等等!
showInfo(20) //恰好!
showInfo(65) //算了吧!
複製代碼
如何定義函數 ?
函數聲明
表達式
建立函數對象 var fun = new Function( ) ;
通常不使用
function fn1 () { //函數聲明
console.log('fn1()')
}
var fn2 = function () { //表達式
console.log('fn2()')
}
fn1();
fn2();
複製代碼
如何調用(執行)函數?
test()
: 直接調用
obj.test()
: 經過對象去調用
new test()
: new調用
test.call/apply(obj)
: 臨時讓test成爲obj對象的方法進行調用
var obj = {} //一個對象
function test2 () { //一個函數
this.xxx = 'lvya'
}
// obj.test2() 不能直接, 根本obj對象中就沒有這樣的函數(方法)
test2.call(obj) // 至關於obj.test2() , 可讓一個函數成爲指定任意對象的方法進行調用
console.log(obj.xxx) //lvya
//這個借調是JS有的,其餘語言作不到。借調就是假設一個對象中沒有一個方法,
//那麼就可讓這個方法成爲想要調用這個方法的對象去使用的方式。
//也就是一個函數能夠成爲指定任意對象的方法進行調用 。
複製代碼
函數也是對象
prototype
call()
/apply()
函數的3種不一樣角色
對象.
調用內部的屬性/方法this是什麼?
如何肯定this的值?
test()
: window
p.test()
: p
new test()
: 新建立的對象
p.call(obj)
: obj
回調函數: 看背後是經過誰來調用的: window/其它
<script type="text/javascript">
function Person(color) {
console.log(this)
this.color = color;
this.getColor = function () {
console.log(this)
return this.color;
};
this.setColor = function (color) {
console.log(this)
this.color = color;
};
}
Person("red"); //this是誰? window
var p = new Person("yello"); //this是誰? p(Person)
p.getColor(); //this是誰? p (Person)
var obj = {};
p.setColor.call(obj, "black"); //this是誰? obj (Object)
var test = p.setColor;
test(); //this是誰? window
function fun1() {
function fun2() {
console.log(this);
}
fun2();
}
fun1(); //this是誰? window
</script>
複製代碼
匿名函數自調用:
(function(w, obj){
//實現代碼
})(window, obj)
複製代碼
;(function () { //匿名函數自調用
var a = 1
function test () {
console.log(++a)
}
window.$ = function () { // 向外暴露一個全局函數
return {
test: test
}
}
})()
$().test() //需明白 1. $是一個函數 2. $執行後返回的是一個對象 3.而後對象.方法()執行函數。
複製代碼
回調函數的理解
回調函數類型 | this是指向誰? |
---|---|
DOM事件回調函數 | 發生事件的DOM元素 |
定時器回調函數 | Window |
ajax請求回調函數 | Window |
生命週期回調函數 | 組件對象 |
函數中的arguments 在調用函數時,瀏覽器每次都會傳遞兩個隱含的參數:
this
arguments
(類數組對象)。這裏的實參是重點,就是執行函數時實際傳入的參數的集合。function foo() {
console.log(arguments); //Arguments(3)返回一個帶實參數據的類數組
console.log(arguments.length); //3 類數組的長度
console.log(arguments[0]); //ya LV 能夠不傳形參,能夠訪問到實參
console.log(arguments.callee); // ƒ foo() {...} 返回對應當前正在執行函數的對象
}
foo('ya LV',18,'male');
複製代碼
arguments妙用1:利用arguments實現方法的重載
a.借用arguments.length屬性來實現
function add() {
var len = arguments.length,
sum = 0;
for(;len--;){
sum += arguments[len];
}
return sum;
}
console.log( add(1,2,3) ); //6
console.log( add(1,3) ); //4
console.log( add(1,2,3,5,6,2,7) ); //26
複製代碼
b.借用prototype屬性來實現
function add() {
return Array.prototype.reduce.call(arguments, function(n1, n2) {
return n1 + n2;
});
};
add(1,2,3,6,8); //20
//三個經常使用的數組的高階函數:map(映射)filter(過濾)reduce(概括)
//能夠參見ES6函數新增特性之箭頭函數進一步優化
複製代碼
arguments妙用2.利用arguments.callee
實現遞歸
先來看看以前咱們是怎麼實現遞歸的,這是一個計算階乘的函數:
function factorial(num) {
if(num<=1) {
return 1;
}else {
return num * factorial(num-1);
}
}
複製代碼
可是當這個函數變成了一個匿名函數時,咱們就能夠利用callee
來遞歸這個函數。
function factorial(num) {
if(num<=1) {
return 1; //若是沒有這個判斷,就會內存溢出
}else {
return num * arguments.callee(num-1);
}
}
console.log(factorial(5)); //120
複製代碼
js一條語句的後面能夠不加分號,相似「能夠加分號可是你們都不加」 的語言就有:Go
, Scala
, Ruby
, Python
, Swift
, Groovy
...
是否加分號是編碼風格問題, 沒有應該不該該,只有你本身喜歡不喜歡
在下面2種狀況下不加分號會有問題
小括號開頭的前一條語句
var a = 3
;(function () {
})()
//若是不加分號就會這麼錯誤解析:
// var a = 3(function () {
// })();
複製代碼
中方括號開頭的前一條語句
var b = 4
;[1, 3].forEach(function () {
})
// 若是不加分號就會這麼錯誤解析:
// var b = 4[3].forEach(function () {
// })
複製代碼
解決辦法: 在行首加分號
強有力的例子: Vue.js
庫。Vue.js
的代碼所有不帶分號。
有一個工具全自動幫你批量添加或者刪除分號:工具地址
像二進制,八進制,十進制,十六進制這些概念在JavaScript中不多被體現出來,但是筆者以爲這個是碼農的素養,因此我以爲有必要再去搞懂。另一個就是原碼反碼補碼的概念。好比在計算機硬件電路中有加法器,全部的運算都會轉爲加法運算,減法就是用加法來實現。因此才引出原碼反碼補碼的概念去解決這一問題。
那麼筆者如今着重講一下位運算符操做和移位操做。js中位運算符有四種:按位取反(~
)、按位與(&
)、按位或(|
)、按位異或(^
)。移位操做有四種:帶符號向右移動(>>
)、無符號向右移動(>>>
)、帶符號向左移動(<<
)、無符號向左移動(<<<
).
示例1:如何快速判斷一個數是否是奇數?
那麼,取餘是你先想到的,那麼還有其餘方法嗎?就是用位運算符去解答。先思考奇數3(二進制是11),偶數4(二進制是100),可知偶數的最低位爲0,奇數的最低位爲1,那麼咱們只要經過某種方法獲得一個數的二進制的最低位,判斷它是否是爲1,是1那這個數就是奇數。
如今的問題就變成了,怎麼獲得一個數的二進制最低位呢?那就是用按位與1(& 1
)去作。假設一個數的二進制爲1111 1111 那麼只要按位與1(1的二進制爲0000 0001)是否是前面一排「與0」都變成0了,只剩最低位了,這樣目標數與1的按位與運算的結果就是等價於目標數的二進制最低位。
var num = 57 ;
if(num & 1){
console.log(num + "是奇數"); //57是奇數
}else{
console.log(num + "是偶數");
}
複製代碼
示例2:怎麼交換兩個number
類型的變量值?
新增一個變量來存儲這種方式是你先想到的,那麼另一種就是經過按位異或操做去交換變量。
異或就是不一樣的爲true(1),相同的爲false(0)。
10^10=0 由於1010 ^ 1010 = 0000
11^0=11 由於1011 ^ 0000=1011
因此獲得兩個結論:
第一,兩個相同的number數異或爲0;第二,任何number數與0異或是其自己。
var a = 10;
var b = 20;
a = a ^ b; //a=10 ^20
b = a ^ b; //b=10 ^20^20 =10 ^(20^20)=10^0=10
a = a ^ b; //a=10 ^20^ 10 =(10^10)^20=0^20=20
console.log(a, b); //20 10 -交換變量成功-
//但這種方法只適用於number數據類型。
//另外能夠用ES6中的數組解構。
複製代碼
示例3:如何計算出一個數字某個二進制位?
在回答這個問題前,咱們先總結出一些結論供咱們使用。移位都是當作32位來移動的,但咱們這裏就簡單操做,用8位來模擬。
先看帶符號向右
移位:
10 >>
1 翻譯題目:10帶符號向右移動一位是幾?
0000 1010 >>
1
0000 0101 這個結果就是移位後的結果。咱們能夠知道0101就是十進制的5.
帶符號向右移動就是總體向右移動一位,高位用符號位去補。正數用0補,負數用1補。
咱們能夠看出結論,帶符號向右移動其實就是往右移動一位,至關於除以2.
如今再來看看帶符號向左移位:
10 <<
2 翻譯題目: 10帶符號向左移動2位是幾?
0000 1010 <<
2
0010 1000 低位用0補。這個 0010 1000 就是數就是40.
咱們能夠看出結論,帶符號向左移動其實就是往左移動一位,至關於乘以2。移動2位便是乘4。
如今迴歸題目,假設我要知道10(1010)的倒數第三位的0這個進制位。
首先往右移動兩位變成0010 , 而後進行 ‘&1
’ 操做 , 0010 &
0001 =0000 =0 ,這個0就是10的二進制位的倒數第三位。因此是經過:(10 >>
2 &
1)的方式獲得10的倒數第三位的進制位。
示例4:如何計算出2的6次方最快算法?
2B程序猿會用2 * 2 * 2 * 2 * 2 * 2
的方法吧。碼農可能會用for
循環去作或者用Math.pow(2,6)
去寫。
可是這些都不是最快的。咱們來看看高級工程師會怎麼寫,哈哈。咱們剛剛獲得過2個結論,其中一個就是帶符號向左移位其實就是往左移動一位,至關於乘以2。 移動2位,就是乘4。"左乘右除"
。那麼如今我是否是能夠對 1 移動6位 不就能夠了嗎?因此就一行代碼:1 <<
6 。
由彙編知識咱們知道,移位是最底層的計算。能夠徹底用加法器實現。而Math.pow(2,6)
其實會有不少的彙編指令才能夠實現這一條代碼。但1 <<
6 只須要一條,因此,性能是很好的。
內存溢出:
一種程序運行出現的錯誤
當程序運行須要的內存超過了剩餘的內存時, 就出拋出內存溢出的錯誤。
// 1. 內存溢出
var obj = {}
for (var i = 0; i < 10000; i++) {
obj[i] = new Array(10000000)
console.log('-----')
}
//直接崩掉了,須要的內存大於目前空閒的內存,直接報錯誤:內存不足。
//就如一個水杯,水倒滿了就溢出,這就是內存溢出。
複製代碼
內存泄露:
佔用的內存沒有及時釋放,這時程序仍是能夠正常運行的
內存泄露積累多了就容易致使內存溢出
常見的內存泄露:
意外的全局變量
// 在乎外的全局變量--在ES5的嚴格模式下就會報錯。
function fn() {
a = new Array(10000000)
console.log(a)
}
fn()
//a就是意外的全局變量,一直會佔着內存,關鍵它仍是指向一個數組很是大的對象。這塊內存就一直佔着。
複製代碼
沒有及時清理的計時器或回調函數
// 沒有及時清理的計時器或回調函數
var intervalId = setInterval(function () { //啓動循環定時器後不清理
console.log('----')
}, 1000)
// clearInterval(intervalId)
複製代碼
閉包
// 閉包
function fn1() {
var a = 4
function fn2() {
console.log(++a)
}
return fn2
}
var f = fn1()
f()
//f指向的fn2函數對象一直都在,設f指向空對象,進而讓fn2成爲垃圾對象,進而去回收閉包。
// f = null
複製代碼
函數節流:讓一段程序在規定的時間內只執行一次
<script type="text/javascript">
window.onload = function () {
// 函數節流: 讓一段程序在規定的時間內只執行一次
let flag = false;
document.getElementsByTagName('body')[0].onscroll = function () {
if(flag){
return;
}
flag = true; //當以前爲FALSE的時候進來,我在2s內纔會有定時器註冊。
setTimeout(function () {
console.log('滾動過程當中,2s只會註冊定時器一次');
flag = false; //一次後,爲第二次作準備。只要是TRUE,我就進不來註冊定時器的邏輯
}, 2000)
}
}
</script>
複製代碼
函數防抖: 讓某一段程序在指定的事件以後觸發
<script type="text/javascript">
//場景:讓滾動完以後2s觸發一次。
window.onload = function () {
// 函數防抖: 讓某一段程序在指定的事件以後觸發
let timeoutId = null;
document.getElementsByTagName('body')[0].onscroll = function () {
timeoutId && clearTimeout(timeoutId); //當第一次來的時候爲null,不須要清除定時器。
timeoutId = setTimeout(function () {
console.log('xxx');
}, 2000) //在這個滾動過程當中的最後一次才註冊成功了,其餘的定時器都註冊完後立刻清除。
}
}
</script>
複製代碼
此文檔爲呂涯原創,可任意轉載,但請保留原連接,標明出處。 文章只在CSDN和掘金第一時間發佈: CSDN主頁:https://blog.csdn.net/LY_code 掘金主頁:https://juejin.im/user/5b220d93e51d4558e03cb948 如有錯誤,及時提出,一塊兒學習,共同進步。謝謝。 😝😝😝