因爲工做需求從新回顧了一下JavaScript,如下內容是我之前的學習筆記和其餘參考資料整理完善後的內容,都是經常使用到的,使用頻率比較高的,本身必須得精通的知識點的總結,便之後再複習參考。javascript
自定義對象時,以構造函數爲模板,對象的屬性和方法,能夠定義在構造函數內部。每當獲取對象時都會在內存中建立新的對象屬性和方法,這既是增長頁面代碼量有浪費內存(系統資源)。html
同一個構造函數的對象實例之間沒法共享屬性,而全部的方法都是一樣的行爲,所以屬性和方法徹底應該共享。但沒法共享這就是缺點。java
function Student(Name,Class){ this.name=Name; this.class=Class; this.f=function(){ console.log(this.name+'是'+this.class+'班的學生'); } } //此時,每當獲得一個對象實例,就會多一個「01」的班,浪費內存, //由於只有一個班,而學生兩個,只在內存中存在一個班級而學生不同就能夠了,但構造函數沒法實現 var Stu1=new Student('Aklman','01'); var Stu2=new Student('Ahmatbek','01'); Stu1.f(); Stu2.f();
JavaScript中每個對象都繼承另外一個對象,父類對象稱之爲「原型」(prototype)對象。只有null除外,其餘都有本身的原型對象程序員
而原型對象上的全部屬性和方法,都能被派生(子類)對象共享。經過構造方法生成生成實例化對象時,會自動生成實例化對象分配原型對象。每個構造方法都有一個prototype屬性,這個屬性就是實例化對象的原型對象。數組
function Student(Name,Class){ this.name=Name; this.f=function(){ console.log(this.name+'是'+this.class+'班的學生'); } } //構造函數的prototype就是由這個構造函數獲得的對象的原型對象(父類對象) Student.prototype.class='01'; var stu1=new Student('aklman'); var stu2=new Student('ahmatbek'); stu1.f(); stu2.f();
構造函數Student的prototype對象就是實例化對象stu1和stu2的原型對象。在原型對象上添加一個color屬性。結果,實例化對象都能讀取屬性。實現屬性共享;瀏覽器
原型對象的屬性不是實例化對象自身的屬性。可是隻要修改原型對象,變更會馬上回提現到全部實例化對象上。閉包
若是實例化對象自身就有某個屬性或方法,那麼原型對象上的屬性和方法便會失效:
function Student(Name,Class){ this.name=Name; this.f=function(){ console.log(this.name+'是'+this.class+'班的學生'); } } //將class屬性,賦值給原型對象,此後全部對象共享class屬性 Student.prototype.class='01'; var stu1=new Student('aklman'); var stu2=new Student('ahmatbek'); //爲stu2對象定義class屬性,原型對象的class屬性則失效,也就是說在stu2上的屬性發生變化 stu2.class='02'; stu1.f(); stu2.f();
總結:原型對象的做用就是定義全部數理化對象共享的屬性和方法。這也是他被稱爲原型對象的緣由,而實例化對象能夠視做從原型對象衍生出來的子對象。因爲JavaScript的全部對象都是構造函數(只有null除外)。而全部構造函數都有prototype屬性(實際上是全部函數都有prototype屬性),因此全部對象都有本身的原型對象。app
對象的屬性和方法,有多是定義在自身內,也有多是定義在它的原型對象上。因爲原型對象自己也是對象,又有本身的原型,全部生成了一條原型鏈(prototype chain)。例如,a對象是b對象的原型,b對象是c對象的原型,依次類推。框架
若是那麼一層層地上溯,全部對象的原型最終均可以上溯到Object對象上。Object對象也有原型就是一個沒有任何屬性和方法的null對象,而null對象沒有本身的原型。函數
獲取對象的原型對象:
function Student(Name){ this.name=Name; } var stu1=new Student('aklman'); console.log(Student.prototype); console.log(Student.prototype.constructor.prototype); //因爲對象自身直接獲取原型對象,最終的大對象就是object,也就是說,在JavaScript中全部對象(無論從何而來)都基於object大對象,而最大對象Object的原型指向null,也就是沒有 console.log(stu1.__pro__.__proto__.__proto__);
原型鏈的做用是讀取對象某個屬性時,JavaScript引擎先尋找對象自己的屬性,若是找不到就找它的原型(父類對象),若是仍是找不到就找原型的原型。若是直到最頂層的Object。prototype仍是找不到,則返回undefined。
若是對象自身和它的原型,都定義了一個同名屬性,那麼清閒讀取對象自身的屬性,這叫 覆蓋(overriding)。
注意:一級級向上,在原型鏈尋找某個屬性,對性能是有影響的。若是尋找某個不存在的屬性將會遍歷整個原型鏈。
實際開發上注意事項:一般使用第三方框架(一個類),可是咱們發現這個類中並不存在咱們想要的屬性或方法時,不能直接修改源代碼,可是能夠經過原型對象來添加咱們想要的屬性和方法。
對象有一個constructor屬性指向原型對象所在的構造函數:
function Student(name){ this.name=name; } var stu=new Student('aklman'); console.log(stu.constructor);
Object.getPrototypeOf()方法返回一個對象的原型對象,也就是經過它獲取原型對象,這是標準的方法。
function Student(name,Class){ this.name=name; } var stu=new Student('aklman'); //獲取stu對象的原型對象 var s=Object.getPrototypeOf(stu); console.log(s);
Object.setPrototypeOd()爲現有對象設置原型對象,第一個參數是現有對象,第二個是要設置成爲原型對象的對象,用這個方法來設置原型對象。
function Student(name,Class){ this.name=name; } var ob={p:'aklan'}; var stu=new Student('Jappar'); //設置stu的原型對象ob Object.setPrototypeOf(stu,ob); console.log(stu.p);//aklman console.log(stu.name);//Jappar console.log(Object.getPrototypeOf(stu));//由大對象提供的getPrototypeOf方法獲取對象的原型對象
proto屬性:前面用proto來獲取原型對象,若是給proto屬性賦值,則設置原型對象;最好不用這個,用Object.getPrototypeOf()來讀取,用Object.setPrototypeof()來設置。也就是用這兩個方法來對原型對象作讀寫操做。
上面寫過獲取原型對象的方法有三種:
obj.__proto__ obj.constructor.prototype Object.getPrototypeOf(obj)
其中前兩個方法不是很友好,最新的ES6標準規定,proto屬性只有瀏覽器才須要部署,其餘環境能夠不部署。而obj.constructor.prototype在手動改變原型對象時,會失效。
function Student(name,Class){ this.name=name; } var ob={p:'aklman'}; var stu=new Student('Jappar'); //使用constructor屬性獲取原型對象 console.log(stu.constructor.prtototype); //修改stu的原型對象爲ob Object.setPrototypeOf(stu,ob); //使用getPrototypeOf方法來查看stu的原型對象爲ob console.log(Object.getPrototypeOf(stu)); //使用constructor屬性獲取原型對象爲Object,這是錯誤的,構造函數沒法修改對象的原型對象 console.log(stu.constructor.prototype);
總結:最好使用Object.getPrototypeOf()方法獲取原型對象,沒了。
function Student(name){ this.name=name; } vat stu=new Student('aklman'); //1.構造函數獲取原型對象 console.log(stu.constructor.prototype); //2.由對象自身獲取原型對象 console.log(stu.__proto__); //3.有大樹提供的getPrototypeOf()方法獲取對象的原型對象 console.log(Obj.getPrototypeOf(stu));
JavaScript有兩種做用域:全局做用域和函數做用域(局部做用域)。函數內部能夠直接讀取全局變量,可是函數外部沒法讀取函數內部聲明的變量。
可是,有時候卻須要在函數外部訪問函數內部的變量;正常狀況下,這是沒法的訪問的,只有經過變通方法才能實現訪問,也就是在函數內部再定義一個函數,經過內部函數來訪問函數內部的變量。
function fun1(){ var n=100; var fun2=function(){ console.log(n); } return funct; } var pack=fun1(); pack();//100
說明:函數fun2就在函數fun1()內部,這是fun1()內部的全部局部變量對fun2()是可訪問的。反過來就不行,內部函數中的局部變量對父類函數是不可訪問的。這就是JavaScript中特有的鏈式做用域(chain scope)結構,子對象會一級一級地向上尋找全部父類對象的變量,因此,父對象的全部變量,對子對象都是可見的,反之則不成立。
既然fun2()能夠讀取fun1()中的局部變量,那麼只要把fun2()做爲返回值,咱們就能夠在fun1()外部讀取它的內部變量了。
重點:
閉包就是函數fun2,既能狗讀取其餘函數內部變量的函數。因爲JavaScript中,只要函數內部的子函數纔可以讀取函數內部的局部變量,所以能夠把閉包簡單理解成「定義一個函數內部的函數」。閉包的最大特色就是它能夠「記住」誕生的環境,好比fun2()記住,因此從fun2()能夠獲得fun1()的內部變量。本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑。
function fun1(){ var n=100; console.log(++n); } fun1();//101 fun1();//101
說明:在函數內部引入一個變量或者函數時,系統都會開闢一塊內存空間;還會將這塊內存的引用計數器初始化,初始化值爲0;若是外部有全局變量或者程序引用了這塊空間,則引用計數器會自動進行+1操做,當函數執行完畢後,變量計數器會從新歸零,系統會運行垃圾回收機制,將函數運行產生的數據銷燬;若是計數器不是0,則不會清楚數據;這過程就是JavaScript的垃圾回收機制;
JavaScript的垃圾回收機制原理圖:
用閉包的分析話更能體驗此原理:
function fun1(){ var n=100; function fun2(){ console.log(++n); } return fun2; } var fu=fun1();//實際上返回了fun2 fu();//101,至關於調用了fun2(), fu();//102,至關於在此調用了fun2() fu();//103 fu();//104
代碼分析(註釋):
因函數fun1被調用時,返回的結果是fun2函數體,也就是說,fun2函數被當作返回值給fun1的調用者,可是fun2函數並無在此被調用執行(只是把函數體返回到函數外了);所以整個fun1函數體,沒法判斷子函數fun2會對其產生何種影響,沒法判斷變量n是否會被使用,即便fun1函數被調用結束,整個fun1函數始終保留在內存中,不會被垃圾回收機制回收;
也就是運行代碼發現,函數調用一次(在這裏是指fun2),其變量n變化一次;
閉包的最大用處:
能夠讀取函數內部的變量。讓函數內部讀取的變量始終保持在內存中,即閉包可使得它誕生環境一直存在。
注意的是,外層函數每次運行,都會生成一個新的閉包,而這個閉包有會保留外層函數的內部變量,因此內存消耗很大;所以不能濫用閉包,不然會形成網頁的性能問題。
關於JavaScript中的this的指向:
1.全局做用域下,this指向window對象2.構造函數中,this指向實例化對象
若是要在調用函數是直接修改函數內部的this指向使用call或者apply方法來修改指向。
1.call方法格式:函數名稱.call(obj,arg1,arg2,...argN);說明其中:obj是函數內this要指向的對象,arg列表是參數列表,參數與參數之間使用一個逗號隔開
var ob1={name:'Aklman',age:23}; var ob2={name:'ahmatbek'}; function fun(sex){ console.log(this.name+this.age+sex); } fun.call(ob1,'男');//Aklman23男 fun.call(ob2,'男');//ahmatbekundefined男,由於對象ob2中沒有age屬性
2.apply方法格式:函數名稱.apply(obj,[arg1,,arg2,...argN]);說明其中:obj是函數內this要指向的對象,arg列表是參數列表,要求格式爲數組
var ob1={name:'aklman',age:23}; var ob2={name:'ahmatbek',tel:176}; function fun(sex,age,tel){ console.log(this.name+age+sex+tel); } fun.apply(ob1, ['男', 23, 176]);//aklman23男176 fun.apply(ob2, ['男', 20, 133]);//ahmatbek20男133
兩種修改this指向方法的區別:
相同點:功能徹底同樣,都是爲了改變函數內部的hits指向,惟一的不一樣就在於參數傳遞方式不一樣不一樣點:call方法可能多個參數,第一個要指向的對象,其餘參數爲函數的實參;apply方法最多隻能有兩個實參,第一個要指向的對象,第二個是數組,數組內容爲函數的實參。
JavaScript與其餘語言不太同樣,它只有兩種屬性,即公有屬性和私有屬性,概念很好理解,也就是在構造函數內部經過this聲明的屬性就是公有屬性,經過var聲明的就是私有屬性。
function Employee(){ this.name='aklman';//公有屬性 var age=18;//私有屬性 this.fun=function(){//公有方法 console.log(this.name+age);//age只能在構造函數內部訪問 } } var m=new Employee(); console.log(m.name);//aklman console.log(m.age);//undefined m.fun();//aklman18
JavaScript中的繼承的實現與其餘語言也不相同,它沒有關鍵字提供繼承的功能。所謂繼承就是爲了子類中提供父類中的屬性和方法,子類可以使用父類中的屬性和方法;
JavaScript中繼承的實現方式有:
1.經過原型對象實現繼承
//聲明構造函數 function Study(){ this.fun=function(){ console.log('我是構造函數中的公有方法'); } } //聲明構造函數 function Student(){} Student.prototype=new Study();//設置構造函數Student的原型爲Study,實現繼承 var extend=new Study(); extend.fun();
2.經過call或apply方法繼承(實質就是改變指向使用父類中的屬性和方法)
//聲明構造函數 function Study(){ this.fun=function(){ console.log('我是構造函數中的公有方法'); } } //聲明構造函數 function Student(){ Study.call(this);//將Study函數內部的this指向Student的實例化對象 } var extend=new Study(); extend.fun();
JavaScript提供定時執行代碼的功能叫作定時器;
1.setTimeout():用來指定某個函數或某代碼,在多少秒以後執行。2.setInterval():指定某個任務每隔一段時間就執行一次,也就是無限次的定時執行。
setTimeout(),setInterval()的第一個參數都是指定執行的函數名稱或者代碼段,第二個參數是時間:
function fun(){ var n=1; return function(){ console.log(++n); } } var s=fun(); //3秒後調用函數; setTimeout('s()',3000); //每隔1秒就執行函數名爲s的函數 setInterval(s,100);
setTimeout()函數和setInterval()函數都返回一個表示計數器編號的整數值,將該整數傳入clearTimeout()和clearInterval()函數,就能夠取消對應的定時器。
<input type="button" value="中止"> <script> function fun(){ var n=1; return function(){ console.log(++n); } } var s=fun(); //3秒後調用函數; var i=setTimeout('s()',3000); //每隔1秒就執行函數名爲s的函數 var j=setInterval(s,100); document.querySelector('input').onclick=function(){ //中止定時器 clearInterval(j); clearTimeout(i); } </script>
上面提過內存,計數器,那麼就谷歌一下什麼是內存及計數器。
一般說的內存是計算機的主存儲器(main memory),簡稱主存。主存經過控制芯片等與CPU相連,主要負責存儲指令和數據。主存由可讀寫的元素構成,每一個字節(1字節=8位)都帶有一個地址編號(也就是所謂的內存地址)。CPU能夠經過改地址讀取主存中的指令和數據,固然也能夠寫入數據。注意的是,主存中存儲的指令和數據會隨着計算機的關機自動清除(自動銷燬,釋放)。
寄存器是CPU的組件之一,,CPU由控制器,運算器,時鐘,寄存器組成。控制器作的是數據運算之外的處理(主要是輸入和輸出的時機控制),好比內存和磁盤等媒介的輸入輸出,顯示器,打印機的輸出等,都是控制器作的事。
廢話很少說,聽大師們說CPU中程序員只要搞明白寄存器就能夠飛上天了。緣由是程序是把寄存器做爲對象來描述,也就是計算機中每一句命令都是經過寄存器才能執行(數據存儲,假髮運算等),寄存器也有不少種類,根據類型存儲的數據也不同,不一樣的CPU中的寄存器數量也不同以及寄存器存儲的數值範圍也不同。
寄存器由程序計數器,標誌寄存器,累加寄存器,基址寄存器,變址寄存器,通用寄存器等組成,其中程序計數器和標誌寄存器比較特殊。計數器以二進制形式技術,計數規則是:CPU每執行一個命令,計數器的值就會自動加1,例如,CPU執行0100地址的指令後,計數器的值就變成了0101(當執行的指令佔據多個內存地址是,增長與指令長度相應的數值)。而後CPU的控制器就會參照計數器的數值,從內存中讀取命令並執行,也就是說,程序計數器決定着程序的運行流程。