最近在系統的學習JS深層次內容,並稍微整理了一下,做爲備忘和後期複習,這裏分享給你們,但願對你們有所幫助。若有錯誤請留言指正,tks。javascript
瞭解這些問題,我先一步步來看,先從稍微淺顯內容提及,而後引出這些概念。html
本文只用實例驗證結果,並作簡要說明,給你們增長些印象,由於單獨一項拿出來都須要大篇幅講解。java
function show(x) { console.log(typeof(x)); // undefined console.log(typeof(10)); // number console.log(typeof('abc')); // string console.log(typeof(true)); // boolean console.log(typeof(function() {})); //function console.log(typeof([1, 'a', true])); //object console.log(typeof({ a: 10, b: 20 })); //object console.log(typeof(null)); //object console.log(typeof(new Number(10))); //object } show();
undefined, number, string, boolean 屬於簡單的值類型,不是對象;
函數、數組、對象、null、new Number(10)都是對象;
判斷一個變量是否是對象很是簡單。值類型的類型判斷用typeof,引用類型的類型判斷用instanceof。chrome
var obj = { a: 10, b: 20 }; var arr = [5, 'x', true];
語法糖實際上是一種「快捷方式」,上面的代碼是一種縮寫,完整的寫法是:數組
var obj = new Object(); obj.a=10; obj.b=20; var arr= new Array(); arr[0]=5; arr[1]='x'; arr[2]=true;
方法一:閉包
var fn = function() { alert(100); }; fn.a = 10; fn.b = function() { alert(123); }; fn.c = { name: "典橙貿易", year: 2016 };
fn是函數,a/b/c是fn建立的對象。app
方法二:函數
var obj = { a: 10, b: 20 }; var arr = [5, 'x', true]; 其本質是: var obj = new Object(); obj.a = 10; obj.b = 20; var arr = new Array(); arr[0] = 5; arr[1] = 'x'; arr[2] = true; 而 console.log(typeof (Object)); // function console.log(typeof (Array)); // function
只有構造函數纔有prototype屬性。
一般咱們自定義的函數都屬於構造函數,因此都有此屬性。
JS運行時環境內置的函數有些不是構造函數,好比alert和Math.sqrt等,就沒有此屬性。
注:構造函數是指有一個內部屬性[[Construct]],經過new能夠建立對象的那些函數。學習
每一個函數function都有一個prototype,即原型。
每一個對象都有一個__proto__,即隱式原型。
只有構造函數纔有prototype屬性。
對象的__proto__指向的是建立它的函數的prototype。
函數的prototype都是被Object建立的,每一個自定義函數建立之初,都會有一個prototype(它是如何被建立的,不得而知!)。
例如:this
function Foo(){}; var foo = new Foo(); Foo.__proto__ === Function.prototype // 任何函數都是由Function建立的,因此任何函數的__proto__都指向Function的prototype foo.__proto__ === Foo.prototype // 對象的__proto__指向的是建立它的函數的prototype Foo.prototype.__proto__ === Object.prototype // 若是是自定義的函數,它的prototype.__proto__ 指向 Object.prototype,由於自定義函數的prototype本質上就是和語法糖 var obj = {} 是同樣的,都是被Object建立。 Object.prototype.__proto__ === null // Object.prototype也是個對象,它的__proto__是null(這是個特例,切記切記) foo.prototype == undefined //只有函數纔有prototype屬性,foo是對象 Object.prototype === (new Object()).__proto__ // 如上所述,Object自己是一個函數,(new Object())是對象 Function.prototype.__proto__ === Object.prototype // Function是一個函數,它的prototype是被Object建立,因此它的Prototype.__proto__指向建立Object.prototype Function.__proto__ === Function.prototype //Function也是一個函數,函數是一種對象,也有__proto__屬性。既然是函數,那麼它必定是被Function建立。因此——Function是被自身建立的 Object.__proto__ === Function.prototype //Object是個函數,也是對象,它的__proto__指向的是建立它的函數的prototype
參考:
http://www.cnblogs.com/wangfupeng1988/p/3978131.html
http://www.cnblogs.com/wangfupeng1988/p/3979290.html
Instanceof運算符的第一個變量是一個對象,暫時稱爲A;第二個變量通常是一個函數,暫時稱爲B。
Instanceof的判斷規則是:
沿着A的__proto__這條線來找,若是B的prototype在這條線上,那麼就返回true,不然返回false。
如:
function Foo(){} var f1 = new Foo(); console.log(f1 instanceof Foo); // true console.log(f1 instanceof Object); // true
分析方法:
f1的__proto__路線A:
(1) f1.__proto__ === Foo.prototype (2) Foo.prototype.__proto__ === Object.prototype (3) Object.prototype.__proto__ === null //到終點
結論:
f1 instanceof Foo :A線路(1)中出現了Foo.prototype
f1 instanceof Object :A線路(2)中出現了Object.prototype
參考:
http://www.cnblogs.com/wangfupeng1988/p/3979533.html
訪問一個對象的屬性時,先在基本屬性中查找,若是沒有,再沿着__proto__這條鏈向上找,這就是原型鏈。
例如:
function Foo(){} var f1 = new Foo(); f1.a = 2; f1.b = 3; Foo.prototype.b = 6; Foo.prototype.c = 7; console.log(f1.a); // 2,a是f1的基本屬性,直接訪問沒問題 console.log(f1.c); // 7,f1的基本屬性中沒有c,沿着原型鏈向上,f1.__proto__是Foo.prototype,而Foo.prototype有c,能夠訪問。 console.log(f1.b); // 3,b是f1的基本屬性,直接訪問沒問題。可Foo.prototype還有一個b,可是不會顯示,這種狀況被稱爲「屬性遮蔽」。
判斷屬性是基本屬性(也叫實例屬性)仍是原型鏈上的屬性(也叫原型屬性)使用hasOwnProperty,通常在for…in..循環中。for…in…會輸出實例屬性和原型屬性。
屬性列表參考實例屬性 vs 原型的屬性 vs 靜態屬性
如:
var item; for(item in f1){ console.log(item); // a,b,c } for(item in f1){ f1.hasOwnProperty(item) && console.log(item); // a,b }
在原型鏈的基礎上,很容易理解繼承。例如在原型鏈的實例中,f1.hasOwnProperty就是繼承自Object.prototype。
過程以下:
Object.prototype中有hasOwnProperty,查找結束,容許訪問hasOwnProperty。
因爲全部的對象的原型鏈都會找到Object.prototype,所以全部的對象都會有Object.prototype的方法。這就是所謂的「繼承」。
另外,每一個函數都有的call,apply方法,和length,arguments,caller等屬性也都是繼承下來的。
定義1:js在執行一段代碼以前,會聲明提早,賦值或賦初始值(即undefined)。因此同一代碼段中,能夠先訪問,再定義,這就是「執行上下文」。
定義2:在執行代碼以前,把將要用到的全部的變量都事先拿出來,有的直接賦值了,有的先用undefined佔個空。
分4種狀況:
第1種狀況:變量、函數表達式——只提早聲明,不提早賦值,默認賦值爲undefined;
console.log(a); // undefined var a=10; console.log(fn1); // undefined var fn1 = function(){};
第2種狀況:this關鍵字——提早聲明並賦值
console.log(this); // Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…}
第3種狀況:函數聲明——提早聲明並賦值
console.log(fn2); // function fn2(){} function fn2(){};
第4種狀況:函數內部會建立本身獨有的上下文環境
函數每被調用一次,都會產生一個新的執行上下文環境。由於不一樣的調用可能就會有不一樣的參數。
另外,每一個函數在定義的時候(不是調用的時候),就已經肯定了函數體內部自由變量的做用域。
如:var a = 10; function fn() { console.log(a); // a是自由變量,函數建立時,就肯定了a要取值的做用域 } function bar(f) { var a = 20; f(); // 10,到建立fn的地方找變量a的做用域,而不是調用它的當前做用域。 } bar(fn);
如【執行上下文/執行上下文環境】所述,js代碼中,會有無數的函數調用和嵌套調用,會產生無數的上下文環境,這麼多上下文環境如何管理,以及如何銷燬而釋放內存呢?這就須要【執行上下文棧】的參與了。
執行上下文棧:執行全局代碼時,會產生一個全局上下文環境,並將該上下文環境壓入棧,每次調用函數都又會產生執行上下文環境,並將該函數上下文環境也壓入棧。當函數調用完成時,該函數上下文環境出棧,並消除該函數上下文環境以及其中的數據,再從新回到全局上下文環境。處於活動狀態的執行上下文環境只有一個。其實這是一個壓棧出棧的過程。
如:
var a = 10, // 1. 進入全局上下文環境 fn, bar = function(x) { var b = 5; fn(x + b); // 3. 進入fn函數上下文環境,併入棧,設爲活動狀態。該函數執行完畢後,bar函數上下文出棧,並及時銷燬,釋放內存 }; fn = function(y) { var c = 5; console.log(y + c); } bar(10); // 2. 進入bar函數上下文環境,併入棧,設爲活動狀態。該函數執行完畢後,bar函數上下文出棧,並及時銷燬,釋放內存
參考:http://www.cnblogs.com/wangfupeng1988/p/3989357.html
this在執行上下文中有着很是重要的做用,這裏應該說一下。
瞭解this,須要記住一點:在函數中this到底取何值,是在函數真正被調用執行的時候肯定的,函數定義的時候肯定不了。
大體分爲如下4種狀況:
狀況1:構造函數,this就表明new出來的對象。
function Foo(){ this.name = '典橙貿易'; this.year = 2016; console.log(this); // Foo {name: "典橙貿易", year: 2016} } var f1 = new Foo(); // 所謂構造函數就是用來new對象的函數。
另外,若是在原型鏈中使用this(Foo.prototype中使用this),this仍然表明new出來的對象。
function Foo(){ this.name = '典橙貿易2016'; } Foo.prototype.getName = function(){ console.log(this); // Foo {name: "典橙貿易2016"} } var f1 = new Foo(); f1.getName();
狀況2:函數做爲對象的一個屬性,而且被對象直接調用時,this指向該對象。
var obj = { name:'典橙貿易', fn:function(){ console.log(this); // Object {name: "典橙貿易"} console.log(this.name); // 典橙貿易 } } obj.fn(); // 直接調用 // 若是不是直接調用,this就指向window var obj = { name: '典橙貿易', fn: function() { console.log(this); // Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…} console.log(this.name); // undefined } } var fn1 = obj.fn; fn1();
狀況3:函數用call或者apply調用,this就是傳入的對象的值
var obj ={ name:'典橙貿易' } var fn = function(){ console.log(this); // Object {name: "典橙貿易"} console.log(this.name); // 典橙貿易 } fn.call(obj); // 或者 // fn.apply(obj);
狀況4:全局 & 調用普通函數,this是window
全局:
console.log(this===window); // true 調用普通函數: var name ="典橙貿易"; var fn = function() { console.log(this); // Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…} console.log(this.name); // 典橙貿易 } fn();
注意:
var obj = { name: '典橙貿易', fn: function() { function f() { console.log(this); // Window {external: Object, chrome: Object, document: document, __ctrip: Object, __SERVERDATE__: Object…} console.log(this.name); // undefined } f(); // 函數f雖然是在obj.fn內部定義的,可是它仍然是一個普通的函數,this仍然指向window。 } } obj.fn();
知識點1:javascript中沒有塊級做用域。
var i = 2; if(i>1){ var name ='典橙貿易'; } console.log(name); // 典橙貿易 for循環的{}也是相似。
知識點2:javascript除了全局做用域以外,只有函數能夠建立本身的做用域,稱爲函數做用域。
如經典的問題:
<ul id = "list"> <li> we </li> <li> sdf </li> <li> cx </li> <li> h </li> <li> z </li> </ul>
var list = document.getElementById('list'); var e = list.getElementsByTagName('li'); var i = 0; 錯誤寫法: for (; i < e.length; i++) { e[i].onclick = function() { console.log(i); } } 正確寫法: for (; i < e.length; i++) { (function(a) { e[i].onclick = function() { console.log(a); } })(i); } 等效於: for (; i < 5; i++) { var Fn = function(a) { e[i].onclick = function() { console.log(a); } } Fn(i); }錯誤的緣由:
e[i].onclick 每次循環,只是賦值給onclick,而for循環是沒有塊級做用域的,因此i的值會不斷累加,直到最大值5,故每次循環都會輸出5。
正確的緣由:
因爲函數能夠建立本身的做用域,並且各個做用域間不會相互影響,因此每次循環Fn(i)都會建立一個特有的函數做用域提供給相應的onclick,而每一個做用於中的a變量也不會相互影響。
知識點3:做用域最大的用處就是隔離變量,不一樣做用域下同名變量不會有衝突。
{ var a = 1, b = 2; function fn1() { var a = 100, b = 200; function fn2() { var a = 1000, b = 2000; } } }三個做用域下都聲明瞭「a和b」變量,可是他們不會有衝突。各自的做用域下,用各自的「a和b」。
在A做用域中使用的變量x,卻沒有在A做用域中聲明(即在其餘做用域中聲明的),對於A做用域來講,x就是一個自由變量。
如:
var x=1; function fn(){ var b=2; console.log(x+b); // 這裏的x就是自由變量 }
自由變量取值規則:要到建立 自由變量所在函數 的那個做用域中取值–是【建立】,而不是【調用】,也不是【父做用域】。
如:
var x = 10; function fn() { console.log(x); // fn建立了自由的函數做用域,因此不管什麼地方調用它,都會輸出10 } function show(f) { var x = 20; (function() { console.log(x); // 要到x所在的匿名函數的做用域中找x,故輸出20 f(); // 要到建立fn函數的做用域中找x,故這裏輸出10,而不是20 })(); } show(fn);
在自由變量的基礎上理解做用域鏈更加容易。
在做用域Fn中使用一個變量A,若是Fn中沒有定義A,則到建立Fn的那個做用域Fm中找,若是仍沒有找到,則繼續到建立Fm的做用域中找……依次類推,直至找到變量A或者到全局做用域中仍未找到 爲止,這種跨越多個做用域的線路,就叫「做用域鏈」。
代碼以下:
{ // var A = 4; // 第4步,這是全局做用域,找到則返回4,若是到這裏仍未找到,就結束,報錯"A is not defined" function Fw() { // var A = 3; // 第3步,在做用域Fw中找,找到則返回3,不然到建立Fw的全局做用域中找 function Fm() { // var A = 2; // 第2步,在做用域Fm中找,找到則返回2,不然到建立Fm的Fw中找 function Fn() { // var A = 1; // 第1步,在當前做用域找,找到則返回1,不然到建立Fn的Fm中找 console.log(A); } return Fn(); } return Fm(); } Fw(); }
注意:
這裏說的建立Fn的那個做用域,而不是調用Fn的那個做用域,也不是「父做用域」。詳情參考【自由變量及其取值規則 > 自由變量取值規則】中的實例。
要想理解閉包,前面的【做用域、自由變量、做用域鏈】三部分是基礎。
閉包的兩種形式:函數做爲返回值、函數做爲參數被傳遞。
第一,函數做爲返回值
function fn() { var max = 10; return function bar(x) { if (x > max) { // max是自由變量,取值規則,參考【自由變量及其取值規則】 console.log(x); } } } var f1 = fn(); f1(15); // 15
第二,函數做爲參數被傳遞
var max = 10, fn = function(x) { if (x > max) { // max是自由變量,取值規則,參考【自由變量及其取值規則】 console.log(x); } }; (function(f) { var max = 100; f(15); // 15 })(fn)
另外,
在【執行上下文棧】中說到,每一個函數執行完畢,都會銷燬其函數上下文環境,並清空數據。可是閉包函數執行完後,上下文環境不會被銷燬。由於閉包中函數會返回或者做爲參數被傳遞,在其餘地方會被用到,一旦銷燬,調用閉包的地方就沒法再使用了。因此閉包會增長內容開銷。
參考:
http://www.cnblogs.com/wangfupeng1988/p/3994065.html