前言本篇文章比較適合3年以上的前端工作者,JS三座大山分別指:原型與原型鏈,作用域及閉包,異步和單線程。 原型與原型鏈說到原型,就不得不提一下構(gòu)造函數(shù),首先我們看下面一個(gè)簡(jiǎn)單的例子: function Dog(name,age){ this.name = name; this.age = age;}let dog1 = new Dog('哈士奇',3);let dog2 = new Dog('泰迪',2); 首先創(chuàng)造空的對(duì)象,再讓this指向這個(gè)對(duì)象,通過this.name進(jìn)行賦值,最終返回this,這其實(shí)也是new 一個(gè)對(duì)象的過程。 其實(shí): let obj = {} 是 let obj = new Object()的語法糖; let arr = [] 是 let arr = new Array()的語法糖; function Dog(){...} 是 let Dog = new Fucntion()的語法糖。 那什么是原型那?在js中,所有對(duì)象都是Object的實(shí)例,并繼承Object.prototype的屬性和方法,但是有一些是隱性的。 我們來看一下原型的規(guī)則: 1.所有的引用類型(包括數(shù)組,對(duì)象,函數(shù))都具有對(duì)象特性;可自由擴(kuò)展屬性。 var obj = {};obj.attribute = '三座大山';var arr = [];arr.attribute = '三座大山';function fn1 () {}fn1.attribute = '三座大山'; 2.所有的引用類型(包括數(shù)組,對(duì)象,函數(shù))都有隱性原型屬性(__proto__),值也是一個(gè)普通的對(duì)象。 console.log(obj.__proto__); 3.所有的函數(shù),都有一個(gè)prototype屬性,值也是一個(gè)普通的對(duì)象。 console.log(obj.prototype); 4.所有的引用類型的__proto__屬性值都指向構(gòu)造函數(shù)的prototype屬性值。 console.log(obj.__proto__ === Object.prototype); // true 5.當(dāng)試圖獲取對(duì)象屬性時(shí),如果對(duì)象本身沒有這個(gè)屬性,那就會(huì)去他的__proto__(prototype)中去尋找。 function Dog(name){ this.name = name; } Dog.prototype.callName = function (){ console.log(this.name,'wang wang'); }let dog1 = new Dog('Three Mountain'); dog1.printName = function (){ console.log(this.name);}dog1.callName(); // Three Mountain wang wangdog1.printName(); // Three Mountain 原型鏈:如下圖。 我找一個(gè)屬性,首先會(huì)在f.__proto__中去找,因?yàn)閷傩灾禐橐粋€(gè)對(duì)象,那么就會(huì)去f.__proto__.__proto__去找,同理如果還沒找到,就會(huì)一直向上去查找,直到結(jié)果為null為止。這個(gè)串起來的鏈即為原型鏈。 作用域及閉包講到作用域,你會(huì)想到什么?當(dāng)然是執(zhí)行上下文。每個(gè)函數(shù)都有自己執(zhí)行上下文的excution context,和variable object。這些環(huán)境用于儲(chǔ)存上下文中的變量,函數(shù)聲明,參數(shù)等。只有函數(shù)才能制造作用域。 PS:for if else 不能創(chuàng)造作用域。 console.log(a) ; // undefinedvar a = 1;//可理解為var a;console.log(a); // undefineda = 1; 執(zhí)行console.log時(shí),a只是被聲明出來,并沒有賦值;所以結(jié)果當(dāng)然是undefined。 this 本質(zhì)上來說,在js里this是一個(gè)指向函數(shù)執(zhí)行環(huán)境的指針。this永遠(yuǎn)指向最后調(diào)用它的對(duì)象,并且在執(zhí)行時(shí)才能獲取值,定義是無法確認(rèn)他的值。 var a = { name : 'A', fn : function (){ console.log (this.name) } } a.fn() // this === a a 調(diào)用了fn() 所以此時(shí)this為aa.fn.call ({name : 'B'}) // this === {name : 'B'} 使用call(),將this的值指定為{name:'B'}var fn1 = a.fn fn1() // this === window雖然指定fn1 = a.fn,但是調(diào)用是有window調(diào)用,所以this 為window this有多種使用場(chǎng)景,下面我會(huì)主要介紹4個(gè)使用場(chǎng)景: 1.作為構(gòu)造函數(shù)執(zhí)行 function Student(name,age) { this.name = name // this === s this.age = age // this === s //return this}var s = new Student('py1988',30) 首先new 字段會(huì)創(chuàng)建一個(gè)空的對(duì)象,然后調(diào)用apply()函數(shù),將this指向這個(gè)空對(duì)象。這樣的話,函數(shù)內(nèi)部的this就會(huì)被空對(duì)象代替。 2.作為普通函數(shù)執(zhí)行 function fn () { console.log (this) // this === window}fn () 3.作為對(duì)象屬性執(zhí)行 var obj = { name : 'A', printName : function () { console.log (this.name) // this === obj }}obj.printName () 4.call(),apply(),bind() 三個(gè)函數(shù)可以修改this的指向,具體請(qǐng)往下看: var name = '小明' , age = '17'var obj = { name : '安妮', objAge : this.age, fun : function () { console.log ( this.name + '今年' + this.age ) } } console.log(obj.objAge) // 17 obj.fun() // 安妮今年undefined var name = '小明' , age = '17' var obj = { name : '安妮', objAge :this.age, fun : function (like,dislike) { console.log (this.name + '今年' + this.age ,'喜歡吃' + like + '不喜歡吃' + dislike) } } var a = { name : 'Jay', age : 23 } obj.fun.call(a,'蘋果','香蕉') // Jay今年23 喜歡吃蘋果不喜歡吃香蕉 obj.fun.apply(a,['蘋果','香蕉']) // Jay今年23 喜歡吃蘋果不喜歡吃香蕉 obj.fun.bind(a,'蘋果','香蕉')() // Jay今年23 喜歡吃蘋果不喜歡吃香蕉 首先call,apply,bind第一個(gè)參數(shù)都是this指向的對(duì)象,call和apply如果第一個(gè)參數(shù)指向null或undefined時(shí),那么this會(huì)指向windows對(duì)象。 call,apply,bind的執(zhí)行方式如上例所示。call,apply都是改變上下文中的this,并且是立即執(zhí)行的。bind方法可以讓對(duì)應(yīng)的函數(shù)想什么時(shí)候調(diào)用就什么時(shí)候調(diào)用。 閉包 閉包的概念很抽象,看下面的例子你就會(huì)理解什么叫閉包了: function a(){ var n = 0; this.fun = function () { n++; console.log(n); };}var c = new a();c.fun(); //1c.fun(); //2 閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。在js中只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量。所以可以簡(jiǎn)單的理解為:定義在內(nèi)部函數(shù)的函數(shù)。 用途主要有兩個(gè): 1)前面提到的,讀取函數(shù)內(nèi)部的變量。 2)讓變量值始終保持在內(nèi)存中。 異步和單線程我們先感受下異步。 console.log('start');setTimeout(function () { console.log('medium');}, 1000);console.log('end'); 使用異步后,打印的順序?yàn)?start-> end->medium。因?yàn)?strong>沒有阻塞。 為什么會(huì)產(chǎn)生異步呢? 首先因?yàn)閖s為單線程,也就是說CPU同一時(shí)間只能處理一個(gè)事務(wù)。得按順序,一個(gè)一個(gè)處理。 如上例所示,第一步:執(zhí)行第一行打印 “start”;第二步:執(zhí)行setTimeout,將其中的函數(shù)分存起來,等待時(shí)間結(jié)束后執(zhí)行;第三步:執(zhí)行最后一行,打印“end”;第四部:處于空閑狀態(tài),查看暫存中,是否有可執(zhí)行的函數(shù);第五步:執(zhí)行分存函數(shù)。 為什么js引擎是單線程? js的主要用途是與用戶互動(dòng),以及操作DOM,這決定它只能是單線程。例:一個(gè)線程要添加DOM節(jié)點(diǎn),一個(gè)線程要?jiǎng)h減DOM節(jié)點(diǎn),容易造成分歧。 為了更好使用多CPU,H5提供了web Worker 標(biāo)準(zhǔn),允許js創(chuàng)建多線程,但是子線程受到主線程控制,而且不得操作DOM。 任務(wù)列隊(duì) 單線程就意味著,所有的任務(wù)都要排隊(duì),前一個(gè)結(jié)束,才會(huì)執(zhí)行后面的任務(wù)。如果列隊(duì)是因?yàn)橛?jì)算量大,CPU忙不過來,倒也算了。但是更多的時(shí)候,CPU是閑置的,因?yàn)镮O設(shè)備處理得很慢,例如 ajax讀取網(wǎng)絡(luò)數(shù)據(jù)。js設(shè)計(jì)者便想到,主線程完全可以不管IO設(shè)備,將其掛起,然后執(zhí)行后面的任務(wù)。等后面的任務(wù)結(jié)束掉,在反過頭來處理掛起的任務(wù)。 好,我們來梳理一下: 1)所有的同步任務(wù)都在主線程上執(zhí)行,行程一個(gè)執(zhí)行棧。 2)除了主線程之外,還存在一個(gè)任務(wù)列隊(duì),只要一步任務(wù)有了運(yùn)行結(jié)果,就在任務(wù)列隊(duì)中植入一個(gè)時(shí)間。 3)主線程完成所有任務(wù),就會(huì)讀取列隊(duì)任務(wù),并將其執(zhí)行。 4)重復(fù)上面三步。 只要主線程空了,就會(huì)讀取任務(wù)列隊(duì),這就是js的運(yùn)行機(jī)制,也被稱為 event loop(事件循環(huán))。 via:https://www.cnblogs.com/peiyu1988/p/8855438.html
|
|