每一個函數都有 prototype 屬性,除了 Function.prototype.bind() ,該屬性指向原型。javascript
每一個對象都有 __proto__ 屬性,指向了建立該對象的構造函數的原型。其實這個屬性指向了 [[prototype]] ,可是 [[prototype]] 是內部屬性,咱們並不能訪問到,因此使用 __proto__ 來訪問。php
對象能夠經過 __proto__ 來尋找不屬於該對象的屬性, __proto__ 將對象鏈接起來組成了原型鏈。css
實例指的就是實例對象,而實例對象能夠經過構造函數建立。實例對象自己就有着__proto__屬性,實例對象的__proto__屬性指向原型對象。html
構造函數與通常函數的區別在於構造函數是用於建立實例對象來使用的,因此構造函數通常都是帶有new運算符的函數。構造函數有着全部函數都有的屬性:prototype。構造函數的prototype屬性指向原型對象。前端
原型對象是由構造函數的prototype屬性和這個構造函數建立的實例對象的__proto__屬性共同指向的一個原型鏈上的對象。若是要判斷一個構造函數與實例對象是否有着共同指向的原型對象,能夠使用instanceof 來判斷,具體用法是 實例對象 instanceof 構造函數。好比引用上面構造函數建立實例對象的例子:obj2 instanceof people,結果返回true。java
原型對象顧名思義它也是一個對象,因此它也有對象的__proto__屬性,那原型對象的__proto__屬性也一樣地會指向它上一層的原型對象,順着下去,原型對象的原型對象可能還有它的上一層原型對象,這樣一直到Object.prototype這個原型對象爲止,這就是整個原型鏈。node
instanceof不只僅判斷實例對象與構造函數是否有着一樣指向,實際上,但凡在這個實例對象的原型鏈上的構造函數與對象之間,使用instanceof來判斷都會返回true,因此若是要找到實例對象是直接由哪一個構造函數建立的,使用instanceof不可行,這能夠使用constructor來替代。好比 obj2 constructor people 就是返回true。android
function createPerson(name, age, job) { let o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function () { console.log(this.name); } return o; } let person1 = createPerson("Nicholas", 29, "Software Engineer"); let person2 = createPerson("Greg", 27, "Doctor");
function Person(name,age,job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { console.log(this.name); } } let person1 = createPerson("Nicholas", 29, "Software Engineer"); let person2 = createPerson("Greg", 27, "Doctor");
使用構造函數的主要問題,就是每一個方法都要在每一個實例上從新建立一遍 (person1.sayName !== person2.sayName)面試
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true
咱們建立的每一個函數都有一個prototype
(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法,可是原型中全部屬性是被實例共享的, 引用類型的屬性會出問題。ajax
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; } Person.prototype = { constructor : Person, sayName : function(){ console.log(this.name); } } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); console.log(person1.friends); //"Shelby,Count,Van" console.log(person2.friends); //"Shelby,Count" console.log(person1.friends === person2.friends); //false console.log(person1.sayName === person2.sayName); //true
建立自定義類型的最多見方式,就是組合使用構造函數模式與原型模式。構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。結果,每一個實例都會有本身的一份實例屬性的副本,但同時又共享着對方法的引用,最大限度地節省了內存。在這個例子中,實例屬性都是在構造函數中定義的,而由全部實例共享的屬性constructor和方法sayName()則是在原型中定義的。而修改了person1.friends(向其中添加一個新字符串),並不會影響到person2.friends,由於它們分別引用了不一樣的數組。 這種構造函數與原型混成的模式,是目前在ECMAScript中使用最普遍、認同度最高的一種建立自定義類型的方法。能夠說,這是用來定義引用類型的一種默認模式。
function Person(name, age, job){ //屬性 this.name = name; this.age = age; this.job = job; if (typeof this.sayName != "function"){ console.log(1); Person.prototype.sayName = function(){ console.log(this.name); }; } } var person1 = new Person("Nicholas", 29, "Software Engineer"); //1 var person2 = new Person("Greg", 27, "Doctor"); person1.sayName(); person2.sayName();
把全部信息都封裝在了構造函數中,而經過在構造函數中初始化原型(僅在必要的狀況下),又保持了同時使用構造函數和原型的優勢。換句話說,能夠經過檢查某個應該存在的方法是否有效,來決定是否須要初始化原型。這裏只在sayName()方法不存在的狀況下,纔會將它添加到原型中。這段代碼只會在初次調用構造函數時纔會執行。此後,原型已經完成初始化,不須要再作什麼修改了。不過要記住,這裏對原型所作的修改,可以當即在全部實例中獲得反映。所以,這種方法確實能夠說很是完美其中,if語句檢查的能夠是初始化以後應該存在的任何屬性或方法——沒必要用一大堆if語句檢查每一個屬性和每一個方法;只要檢查其中一個便可。對於採用這種模式建立的對象,還能夠使用instanceof
操做符肯定它的類型。
// 定義一個動物類
function Animal (name) {
// 屬性
this.name = name || 'Animal'; // 實例方法 this.sleep = function(){ console.log(this.name + '正在睡覺!'); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + '正在吃:' + food); }; --原型鏈繼承 function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = 'cat'; // Test Code var cat = new Cat(); console.log(cat.name);
function Cat(name){
Animal.call(this); this.name = name || 'Tom'; } // Test Code var cat = new Cat(); console.log(cat.name);
function Cat(name){
Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat; // Test Code var cat = new Cat(); console.log(cat.name);
組合繼承最大的問題就是不管什麼狀況下,都會調用兩次父類構造函數: 一次是在建立子類型原型的時候, 另外一次是在子類型構造函數內部. 寄生組合式繼承就是爲了下降調用父類構造函數的開銷而出現的.
其背後的基本思路是: 沒必要爲了指定子類型的原型而調用超類型的構造函數
function extend(subClass, superClass) {
subClass.prototype = superClass.prototype; subClass.superclass = superClass.prototype; if(superClass.prototype.constructor == Object.prototype.constructor) { superClass.prototype.constructor = superClass; } } function Father(name){ this.name = name; this.colors = ["red","blue","green"]; } Father.prototype.sayName = function(){ alert(this.name); }; function Son(name,age){ Father.call(this,name);//繼承實例屬性,第一次調用Father() this.age = age; } extend(Son,Father)//繼承父類方法,此處並不會第二次調用Father() Son.prototype.sayAge = function(){ alert(this.age); } var instance1 = new Son("louis",5); instance1.colors.push("black"); console.log(instance1.colors);//"red,blue,green,black" instance1.sayName();//louis instance1.sayAge();//5 var instance1 = new Son("zhai",10); console.log(instance1.colors);//"red,blue,green" instance1.sayName();//zhai instance1.sayAge();//10
爲了追本溯源, 我順便研究了new運算符具體幹了什麼?發現其實很簡單,就幹了三件事情.
var obj = {};
obj.__proto__ = F.prototype; F.call(obj);
第一行,咱們建立了一個空對象obj;
第二行,咱們將這個空對象的__proto__成員指向了F函數對象prototype成員對象;
第三行,咱們將F函數對象的this指針替換成obj,而後再調用F函數.
咱們能夠這麼理解: 以 new 操做符調用構造函數的時候,函數內部實際上發生如下變化:
一、建立一個空對象,而且 this 變量引用該對象,同時還繼承了該函數的原型。
二、屬性和方法被加入到 this 引用的對象中。
三、新建立的對象由 this 所引用,而且最後隱式的返回 this.
function create(Con, ...args) { let obj = {} Object.setPrototypeOf(obj, Con.prototype) let result = Con.apply(obj, args) return result instanceof Object ? result : obj }
文檔對象模型(Document Object Model,簡稱DOM),是W3C組織推薦的處理可擴展標誌語言的標準編程接口。在網頁上,組織頁面(或文檔)的對象被組織在一個樹形結構中,用來表示文檔中對象的標準模型就稱爲DOM
createDocumentFragment() //建立一個DOM片斷 createElement() //建立一個具體的元素 createTextNode() //建立一個文本節點
添加:appendChild()
移出:removeChild()
替換:replaceChild()
插入:insertBefore()
複製:cloneNode(true)
節點變化觸發的事件
//查找 getElementsByTagName() //經過標籤名稱 getElementsByClassName() //經過標籤名稱 getElementsByName() //經過元素的Name屬性的值 getElementById() //經過元素Id,惟一性
子節點
Node.childNodes
//獲取子節點列表NodeList; 注意換行在瀏覽器中被算做了text節點,若是用這種方式獲取節點列表,須要進行過濾Node.firstChild
//返回第一個子節點Node.lastChild
//返回最後一個子節點父節點
Node.parentNode
// 返回父節點Node.ownerDocument
//返回祖先節點(整個document)同胞節點
Node.previousSibling
// 返回前一個節點,若是沒有則返回nullNode.nextSibling
// 返回後一個節點DOM事件模型分爲捕獲和冒泡。一個事件發生後,會在子元素和父元素之間傳播(propagation)。這種傳播分紅三個階段。
window
對象自上而下目標節點傳播的階段;目標
節點自下而上向window
對象傳播的階段。捕獲是從上到下,事件先從window
對象,而後再到document
(對象),而後是html
標籤(經過document.documentElement獲取html標籤),而後是body
標籤(經過document.body獲取body標籤),而後按照普通的html結構一層一層往下傳,最後到達目標元素
。咱們只須要將addEventListener
的第三個參數改成true就能夠實現事件捕獲。
//摘自xyyojl的《深刻理解DOM事件機制》 <!-- CSS 代碼 --> <style> body{margin: 0;} div{border: 1px solid #000;} #grandfather1{width: 200px;height: 200px;} #parent1{width: 100px;height: 100px;margin: 0 auto;} #child1{width: 50px;height: 50px;margin: 0 auto;} </style> <!-- HTML 代碼 --> <div id="grandfather1"> 爺爺 <div id="parent1"> 父親 <div id="child1">兒子</div> </div> </div> <!-- JS 代碼 --> <script> var grandfather1 = document.getElementById('grandfather1'), parent1 = document.getElementById('parent1'), child1 = document.getElementById('child1'); grandfather1.addEventListener('click',function fn1(){ console.log('爺爺'); },true) parent1.addEventListener('click',function fn1(){ console.log('爸爸'); },true) child1.addEventListener('click',function fn1(){ console.log('兒子'); },true) /* 當我點擊兒子的時候,觸發順序是爺爺 ——》父親——》兒子 */ // 請問fn1 fn2 fn3 的執行順序? // fn1 fn2 fn3 or fn3 fn2 fn1 </script>
所謂事件冒泡就是事件像泡泡同樣從最開始生成的地方一層一層往上冒。咱們只須要將addEventListener
的第三個參數改成false就能夠實現事件冒泡。
//html、css代碼同上,js代碼只是修改一下而已 var grandfather1 = document.getElementById('grandfather1'), parent1 = document.getElementById('parent1'), child1 = document.getElementById('child1'); grandfather1.addEventListener('click',function fn1(){ console.log('爺爺'); },false) parent1.addEventListener('click',function fn1(){ console.log('爸爸'); },false) child1.addEventListener('click',function fn1(){ console.log('兒子'); },false) /* 當點擊兒子的時候,觸發順序:兒子——》爸爸——》爺爺 */ // 請問fn1 fn2 fn3 的執行順序? // fn1 fn2 fn3 or fn3 fn2 fn1
因爲事件會在冒泡階段向上傳播到父節點,所以能夠把子節點的監聽函數定義在父節點上,由父節點的監聽函數統一處理多個子元素的事件。這種方法叫作事件的代理(delegation)
。
若是給每一個列表項一一都綁定一個函數,那對於內存消耗是很是大的,效率上須要消耗不少性能。藉助事件代理,咱們只須要給父容器ul綁定方法便可,這樣無論點擊的是哪個後代元素,都會根據冒泡傳播的傳遞機制,把容器的click行爲觸發,而後把對應的方法執行,根據事件源,咱們能夠知道點擊的是誰,從而完成不一樣的事。
在不少時候,咱們須要經過用戶操做動態的增刪列表項元素,若是一開始給每一個子元素綁定事件,那麼在列表發生變化時,就須要從新給新增的元素綁定事件,給即將刪去的元素解綁事件,若是用事件代理就會省去不少這樣麻煩。
標準事件對象:
type
:事件類型target
:事件目標stopPropagation()
方法:阻止事件冒泡preventDefault()
方法:阻止事件的默認行爲IE中的事件對象:
(1)type
:事件類型
(2)srcElement
:事件目標
(3)cancelBubble
屬性:阻止事件冒泡 true表示阻止冒泡,false表示不阻止
(4)returnValue
屬性:阻止事件的默認行爲
它是JS的一個內置對象,常被人們所忽略,但實際上確很重要,JS不像JAVA是顯示傳遞參數,JS傳的是形參,能夠傳也能夠不傳,若方法裏沒有寫參數卻傳入了參數,那麼就要用arguments來拿到這些參數了。每個函數都有一個arguments對象,它包括了函數所要調的參數,一般咱們把它看成數組使用,用它的length獲得參數數量,但它卻不是數組,若使用push添加數據將報錯。
在函數調用的時候,瀏覽器每次都會傳遞進兩個隱式參數:
1. 函數的上下文對象this
2. 封裝實參的對象arguments
arguments還有屬性callee,length和迭代器Symbol
1. 咱們發現callee的值是函數fun,而且callee指向函數fun
function fun(){ // console.log(arguments); console.log('arguments.callee === fun的值:',arguments.callee === fun); } fun('tom',[1,2,3],{name:'Janny'});
2. 第二個屬性length,咱們常常在數組或者類數組中看到,能夠看到arguments的原型索引__proto__的值爲Object,故此咱們推測arguments不是數組,而是一個類數組對象。
3. 第三個屬性是個Symbol類型的鍵,該類型的值都是獨一無二的,該鍵指向的值是一個values函數,該值是一個生成迭代器的函數。
let arr = ['a', 'b', 'c']; let iter = arr[Symbol.iterator](); iter.next() // { value: 'a', done: false } iter.next() // { value: 'b', done: false } iter.next() // { value: 'c', done: false } iter.next() // { value: undefined, done: true }
在arguments中有一樣的做用
function fun(){ console.log(arguments[Symbol.iterator]); let iterator = arguments[Symbol.iterator](); console.log('iterator:',iterator); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); console.log(iterator.next()); } fun('tom',[1,2,3],{name:'Janny'});
Boolean
Null
Undefined
Number
String
Symbol
(ECMAScript 6 新定義)Object
在JavaScript
中,能夠對對象的權限進行配置,經過配置,可將對象設置爲不可擴展對象、密封對象、凍結對象等,以達到保護對象屬性的目的。
[[configurable]]
特性將被設置成false
(意味着不能刪除屬性和方法,可是可修改已有屬性值),使用Object.seal()能夠將對象密封差別:
null
轉爲數字類型值爲0,而undefined轉爲數字類型爲NaN(Not a Number)
undefined
是表明調用一個值而該值卻沒有賦值,這時候默認則爲undefined
null
是一個很特殊的對象,最爲常見的一個用法就是做爲參數傳入(說明該參數不是對象)null
的變量或者對象會被內存收集器回收null instanceof Object null === undefined null == undefined NaN == NaN 0 == "0" true == "20" //答案是: false false true false true false
//加法運算 console.dir(16+"5"); //156 console.dir(5+"a");//5a console.dir(5+NaN);//NaN console.dir(5+null);//5 console.dir('5'+null);//5null console.dir(5+undefined);//NaN console.dir(null+undefined);//NaN console.dir(5+5);//10 console.dir("兩個數的和是"+5+5);//兩個數的和是55 console.dir("兩個數的和是"+(5+5));//兩個數的和是10
首先看雙等號先後有沒有NaN,若是存在NaN,一概返回false。
再看雙等號先後有沒有布爾,有布爾就將布爾轉換爲數字。(false是0,true是1)
接着看雙等號先後有沒有字符串, 有三種狀況:
若是是數字,對方是對象,對象取valueOf()或者toString()進行比較, 其餘一概返回false
null, undefined不會進行類型轉換, 但它們倆相等。
var undefined; undefined == null; // true 1 == true; // true 2 == true; // false 0 == false; // true 0 == ' '; // true NaN == NaN; // false [] == false; // true [] == ![]; // true // alert(!![]) //true // alert(![]) //false // alert([] == 0) //true // alert(false == 0) //true
如今來探討 [] == ! [] 的結果爲何會是true
①、根據運算符優先級 ,! 的優先級是大於 == 的,因此先會執行 ![]
!可將變量轉換成boolean類型,null、undefined、NaN以及空字符串('')取反都爲true,其他都爲false。
因此 ! [] 運算後的結果就是 false
也就是 [] == ! [] 至關於 [] == false
②、根據上面提到的規則(若是有一個操做數是布爾值,則在比較相等性以前先將其轉換爲數值——false轉換爲0,而true轉換爲1),則須要把 false 轉成 0
也就是 [] == ! [] 至關於 [] == false 至關於 [] == 0
③、根據上面提到的規則(若是一個操做數是對象,另外一個操做數不是,則調用對象的valueOf()方法,用獲得的基本類型值按照前面的規則進行比較,若是對象沒有valueOf()方法,則調用 toString())
而對於空數組,[].toString() -> '' (返回的是空字符串)
也就是 [] == 0 至關於 '' == 0
④、根據上面提到的規則(若是一個操做數是字符串,另外一個操做數是數值,在比較相等性以前先將字符串轉換爲數值)
Number('') -> 返回的是 0
至關於 0 == 0 天然就返回 true了
總結一下:
[] == ! [] -> [] == false -> [] == 0 -> '' == 0 -> 0 == 0 -> true
那麼對於 {} == !{} 也是同理的
關鍵在於 {}.toString() -> NaN(返回的是NaN)
根據上面的規則(若是有一個操做數是NaN,則相等操做符返回 false)
總結一下:
{} == ! {} -> {} == false -> {} == 0 -> NaN == 0 -> false
關係類型:
console.dir(16>"5"); //true console.dir("16">"5");//false console.dir(5<"a");//false console.dir(5>=NaN);//false console.dir(5<NaN);//false console.dir(NaN>=NaN);//false console.dir(5>=null);//true console.dir(5>=undefined);//false console.dir(5>=5);//true console.dir(5>=true);//true console.dir(5>="true");//false console.dir(5>="");//true console.dir("Brick">"alphabet");//false B的字符串編碼值是66 ,而a的字符串編碼是97.所以false console.dir("brick">"alphabet");//true 小寫字母b比a大,因此是true
typeof:用來判斷各類數據類型。
typeof 2 //輸出 number typeof null //輸出 object typeof {} //輸出 object typeof [] //輸出 object typeof (function(){}) //輸出 function typeof undefined //輸出 undefined typeof '222' //輸出 string typeof true //輸出 boolean
instanceof:判斷已知對象類型的方法.instanceof 後面必定要是對象類型,而且大小寫不能錯,該方法適合一些條件選擇或分支。
var c= [1,2,3]; var d = new Date(); var e = function(){alert(111);}; var f = function(){this.name="22";}; console.log(c instanceof Array) //true console.log(d instanceof Date) //true console.log(e instanceof Function) //true // console.log(f instanceof function ) //false
instanceof是一個二元運算符,如:A instanceof B. 其中,A必須是一個合法的JavaScript對象,B必須是一個合法的JavaScript函數 (function)。若是函數B在對象A的原型鏈 (prototype chain) 中被發現,那麼instanceof操做符將返回true,不然返回false.
console.log(Array instanceof Function);//true console.log(Object instanceof Function);//true
function Foo() { } var foo = new Foo(); alert(foo instanceof Foo);// true alert(foo instanceof Object);// true alert(foo instanceof Function);// false alert(Foo instanceof Function);// true alert(Foo instanceof Object);// true
爲什麼Object instanceof Function和Function instanceof Object都返回true?
Object, Function, Array等等這些都被稱做是構造「函數」,他們都是函數。而全部的函數都是構造函數Function的實例。從原型鏈機制的的角度來講,那就是說全部的函數都能經過原型鏈找到建立他們的Function構造函數的構造原型Function.protorype對象,因此:
與此同時,又由於Function.prototype是一個對象,因此他的構造函數是Object. 從原型鏈機制的的角度來講,那就是說全部的函數都能經過原型鏈找到建立他們的Object構造函數的構造原型Object.prototype對象,因此:
有趣的是根據咱們經過原型鏈機制對instanceof進行的分析,咱們不可貴出一個結論:Function instanceof Function 依然返回true, 原理是同樣的
1. Function是構造函數,因此它是函數對象
2. 函數對象都是由Function構造函數建立而來的,原型鏈機制解釋爲:函數對象的原型鏈中存在Function.prototype
3. instanceof查找原型鏈中的每個節點,若是Function.prototype的構造函數Function的原型鏈中被查到,返回true
所以下面代碼依然返回true
instanceof部分摘自:https://www.cnblogs.com/objectorl/archive/2010/01/11/Object-instancof-Function-clarification.html
constructor:據對象的constructor判斷,返回對建立此對象的數組函數的引用。
var c= [1,2,3]; var d = new Date(); var e = function(){alert(111);}; alert(c.constructor === Array) //----------> true alert(d.constructor === Date) //-----------> true alert(e.constructor === Function) //-------> true //注意: constructor 在類繼承時會出錯
prototype:全部數據類型都可判斷:Object.prototype.toString.call,這是對象的一個原生原型擴展函數,用來更精確的區分數據類型。
var gettype=Object.prototype.toString gettype.call('aaaa') //輸出 [object String] gettype.call(2222) //輸出 [object Number] gettype.call(true) //輸出 [object Boolean] gettype.call(undefined) //輸出 [object Undefined] gettype.call(null) //輸出 [object Null] gettype.call({}) //輸出 [object Object] gettype.call([]) //輸出 [object Array] gettype.call(function(){}) //輸出 [object Function]
變量做用域:一個變量能夠使用的範圍
JS中首先有一個最外層的做用域:稱之爲全局做用域
JS中還能夠經過函數建立出一個獨立的做用域,其中函數能夠嵌套,因此做用域也能夠嵌套
詞法做用域就是在你寫代碼時將變量和塊做用域寫在哪裏來決定,也就是詞法做用域是靜態的做用域,在你書寫代碼時就肯定了。
做用域鏈是由當前做用域與上層一系列父級做用域組成,做用域的頭部永遠是當前做用域,尾部永遠是全局做用域。做用域鏈保證了當前上下文對其有權訪問的變量的有序訪問。
做用域鏈的意義:查找變量(肯定變量來自於哪裏,變量是否能夠訪問)
引擎會在解釋javascript代碼以前首先對其進行編譯。編譯階段中的一部分工做就是找到全部的聲明,並用合適的做用域將它們關聯起來,引擎查詢共分爲兩種:LHS查詢和RHS查詢
從字面意思去理解,當變量出如今賦值操做的左側時進行LHS查詢,出如今右側時進行RHS查詢,更準確地講,RHS查詢與簡單地查找某個變量的值沒什麼區別,而LHS查詢則是試圖找到變量的容器自己,從而能夠對其賦值。
function foo(a){ console.log(a);//2 } foo( 2 );
這段代碼中,總共包括4個查詢,分別是:
一、foo(...)對foo進行了RHS引用
二、函數傳參a = 2對a進行了LHS引用
三、console.log(...)對console對象進行了RHS引用,並檢查其是否有一個log的方法
四、console.log(a)對a進行了RHS引用,並把獲得的值傳給了console.log(...)
在當前做用域中沒法找到某個變量時,引擎就會在外層嵌套的做用域中繼續查找,直到找到該變量,或抵達最外層的做用域(也就是全局做用域)爲止。
RHS查詢
【1】若是RHS查詢失敗,引擎會拋出ReferenceError(引用錯誤)異常
//對b進行RHS查詢時,沒法找到該變量。也就是說,這是一個「未聲明」的變量 function foo(a){ a = b; } foo();//ReferenceError: b is not defined
【2】若是RHS查詢找到了一個變量,但嘗試對變量的值進行不合理操做,好比對一個非函數類型值進行函數調用,或者引用null或undefined中的屬性,引擎會拋出另一種類型異常:TypeError(類型錯誤)異常
function foo(){ var b = 0; b(); } foo();//TypeError: b is not a function
LHS查詢
【1】當引擎執行LHS查詢時,若是沒法找到變量,全局做用域會建立一個具備該名稱的變量,並將其返還給引擎
function foo(){ a = 1; } foo(); console.log(a);//1
【2】若是在嚴格模式中LHS查詢失敗時,並不會建立並返回一個全局變量,引擎會拋出同RHS查詢失敗時相似的ReferenceError異常
function foo(){ 'use strict'; a = 1; } foo(); console.log(a);//ReferenceError: a is not defined
原理解析:
function foo(a){ console.log(a); } foo(2);
【1】引擎須要爲foo(...)函數進行RHS引用,在全局做用域中查找foo。成功找到並執行
【2】引擎須要進行foo函數的傳參a=2,爲a進行LHS引用,在foo函數做用域中查找a。成功找到,並把2賦值給a
【3】引擎須要執行console.log(...),爲console對象進行RHS引用,在foo函數做用域中查找console對象。因爲console是個內置對象,被成功找到
【4】引擎在console對象中查找log(...)方法,成功找到
【5】引擎須要執行console.log(a),對a進行RHS引用,在foo函數做用域中查找a,成功找到並執行
【6】因而,引擎把a的值,也就是2傳到console.log(...)中
【7】最終,控制檯輸出2
function fn1() { var name = 'iceman'; function fn2() { console.log(name); } return fn2; } var fn3 = fn1(); fn3();
fn2
的詞法做用域能訪問fn1
的做用域
將fn2
當作一個值返回
fn1
執行後,將fn2
的引用賦值給fn3
執行fn3
,輸出了變量name
正常來講,當fn1
函數執行完畢以後,其做用域是會被銷燬的,而後垃圾回收器會釋放那段內存空間。而閉包卻很神奇的將fn1
的做用域存活了下來,fn2
依然持有該做用域的引用,這個引用就是閉包。
棧內存提供一個執行環境,即做用域,包括全局做用域和私有做用域,那他們何時釋放內存的?
通常狀況下,函數執行會造成一個新的私有的做用域,當私有做用域中的代碼執行完成後,咱們當前做用域都會主動的進行釋放和銷燬。但當遇到函數執行返回了一個引用數據類型的值,而且在函數的外面被一個其餘的東西給接收了,這種狀況下通常造成的私有做用域都不會銷燬。
//經典面試題 function outer(){ var num=0;//內部變量 return function add(){//經過return返回add函數,就能夠在outer函數外訪問了 num++;//內部函數有引用,做爲add函數的一部分了 console.log(num); }; } var func1=outer(); func1();//其實是調用add函數, 輸出1 func1();//輸出2 由於outer函數內部的私有做用域會一直被佔用 var func2=outer(); func2();// 輸出1 每次從新引用函數的時候,閉包是全新的。 func2();// 輸出2
JS模塊:具備特定功能的js文件,將全部的數據和功能都封裝在一個函數內部(私有的),只向外暴露一個包信n個方法的對象或函數,模塊的使用者,只須要經過模塊暴露的對象調用方法來實現對應的功能。
應用閉包的主要場合是:設計私有的方法和變量。
一般是由於只想在for循環內部的上下文中使用變量i,但實際上i能夠在全局做用域中訪問,污染了整個做用域:
for (var i= 0; i<10; i++) { console.log(i); } console.log(i);//10
//當即執行匿名函數(IIFE) (function(){ var i = 1; })(); console.log(i);//ReferenceError: i is not defined //for循環的代碼中變量i用let聲明,將會避免做用域污染問題 for (let i= 0; i<10; i++) { console.log(i); } console.log(i);////ReferenceError: i is not defined
下面代碼中,因爲閉包只能取得包含函數中的任何變量的最後一個值,因此控制檯輸出5,而不是0
var a = []; for(var i = 0; i < 5; i++){ a[i] = function(){ return i; } } console.log(a[0]());//5 //能夠經過函數傳參,來保存每次循環的值 var a = []; for(var i = 0; i < 5; i++){ a[i] = (function(j){ return function(){ return j; } })(i); } console.log(a[0]());//0 //而使用let則更方便,因爲let循環有一個從新賦值的過程,至關於保存了每一次循環時的值 var a = []; for(let i = 0; i < 5; i++){ a[i] = function(){ return i; } } console.log(a[0]());//0
var a = 2 ; 這個代碼片斷實際上包括兩個操做: var a 和 a = 2 ,第一個定義聲明是在編譯階段由編譯器進行的。第二個賦值操做會被留在原地等待引擎在執行階段執行。
console.log(a); var a = 0; function fn(){ console.log(b); var b = 1; function test(){ console.log(c); var c = 2; } test(); } fn();
//變量聲明提高後,變成下面這樣 var a ; console.log(a); a = 0; function fn(){ var b; console.log(b); b = 1; function test(){ var c ; console.log(c); c = 2; } test(); } fn();
函數聲明會提高,但函數表達式卻不會提高
foo(); function foo(){ console.log(1);//1 } //提高後 function foo(){ console.log(1); } foo(); //函數表達式不會提高 foo(); var foo = function(){ console.log(1);//TypeError: foo is not a function } //變量提高後,代碼以下所示,依然會報錯: var foo; foo(); foo = function(){ console.log(1); }
函數聲明和變量聲明都會被提高。可是,函數聲明會覆蓋變量聲明
var a; function a(){} console.log(a);//'function a(){}'
可是,若是變量存在賦值操做,則最終的值爲變量的值
var a=1; function a(){} console.log(a);//1 var a; function a(){}; console.log(a);//'function a(){}' a = 1; console.log(a);//1
注意:變量的重複聲明是無用的,但函數的重複聲明會覆蓋前面的聲明(不管是變量仍是函數聲明)
【1】變量的重複聲明無用
var a = 1; var a; console.log(a);//1
【2】因爲函數聲明提高優先於變量聲明提高,因此變量的聲明無做用
var a; function a(){ console.log(1); } a();//1
【3】後面的函數聲明會覆蓋前面的函數聲明
a();//2 function a(){ console.log(1); } function a(){ console.log(2); }
本部分參考博主連接:http://www.javashuo.com/article/p-fcyvnpug-cv.html
狀態 | 描述 |
---|---|
0 | 請求未初始化(在調用 open() 以前) |
1 | 請求已提出(調用 send() 以前) |
2 | 請求已發送(這裏一般能夠從響應獲得內容頭部) |
3 | 請求處理中(響應中一般有部分數據可用,可是服務器尚未完成響應) |
4 | 請求已完成(能夠訪問服務器響應並使用它) |
var Ajax = { get: function(url, fn) { //建立XMLHttpRequest對象 var xhr = new XMLHttpRequest(); //true表示異步 xhr.open('GET', url, true); xhr.onreadystatechange = function() { // readyState == 4說明請求已完成 if(xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304) { //responseText:從服務器得到數據 fn.call(this, xhr.responseText); } }; xhr.send(); }, post: function(url, data, fn) { //datat應爲'a=a1&b=b1'這種字符串格式 var xhr = new XMLHttpRequest(); xhr.open("POST", url, true); // 添加http頭,發送信息至服務器時內容編碼類型 xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.onreadystatechange = function() { if(xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) { fn.call(this, xhr.responseText); } }; xhr.send(data); }
}
淺拷貝就是把屬於源對象的值都複製一遍到新的對象,不會開闢二者獨立的內存區域;深度拷貝則是完徹底全兩個獨立的內存區域,互不干擾
//js的深拷貝
function deepCopy(obj){
//判斷是不是簡單數據類型,
if(typeof obj == "object"){ //複雜數據類型 var result = obj.constructor == Array ? [] : {}; for(let i in obj){ result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i]; } }else { //簡單數據類型 直接 == 賦值 var result = obj; } return result; }
/** * deep clone * @param {[type]} parent object 須要進行克隆的對象 * @return {[type]} 深克隆後的對象 */ const clone = parent => { // 維護兩個儲存循環引用的數組 const parents = []; const children = []; const _clone = parent => { if (parent === null) return null; if (typeof parent !== 'object') return parent; let child, proto; if (isType(parent, 'Array')) { // 對數組作特殊處理 child = []; } else if (isType(parent, 'RegExp')) { // 對正則對象作特殊處理 child = new RegExp(parent.source, getRegExp(parent)); if (parent.lastIndex) child.lastIndex = parent.lastIndex; } else if (isType(parent, 'Date')) { // 對Date對象作特殊處理 child = new Date(parent.getTime()); } else { // 處理對象原型 proto = Object.getPrototypeOf(parent); // 利用Object.create切斷原型鏈 child = Object.create(proto); } // 處理循環引用 const index = parents.indexOf(parent); if (index != -1) { // 若是父數組存在本對象,說明以前已經被引用過,直接返回此對象 return children[index]; } parents.push(parent); children.push(child); for (let i in parent) { // 遞歸 child[i] = _clone(parent[i]); } return child; }; return _clone(parent); };
懶加載即延遲,對於圖片過多的頁面,爲了加快頁面加載速度,咱們須要將頁面內未出如今可視區域內的圖片先不作加載, 等到滾動到可視區域後再去加載。這樣一來頁面加載性能大幅提高,提升了用戶體驗。
<img src="https://i.loli.net/2017/08/08/5989307b6c87b.gif" data-xxx="${data.content[i].url}"> let images = document.querySelectorAll('img[data-xxx]') for(let i = 0; i <images.length; i++){ if(出如今屏幕裏(images[i])){ images[i].src = images[i].getAttribute('data-xxx') images[i].removeAttribute('data-xxx') }
預加載:圖片預加載就是在網頁所有加載以前,提早加載圖片。當用戶須要查看時可直接從本地緩存中渲染,以提供給用戶更好的體驗,減小等待的時間。不然,若是一個頁面的內容過於龐大,沒有使用預加載技術的頁面就會長時間的展示爲一片空白,這樣瀏覽者可能覺得圖片預覽慢而沒興趣瀏覽,把網頁關掉,這時,就須要圖片預加載。固然這種作法實際上犧牲了服務器的性能換取了更好的用戶體驗。
實現預載的方法很是多,能夠用CSS(background)、JS(Image)、HTML(<img />)均可以。經常使用的是new Image();,設置其src來實現預載,再使用onload方法回調預載完成事件。只要瀏覽器把圖片下載到本地,一樣的src就會使用緩存,這是最基本也是最實用的預載方法。當Image下載完圖片頭後,會獲得寬和高,所以能夠在預載前獲得圖片的大小(我所知的方法是用記時器輪循寬高變化)。通常實現預載的工具類,都實現一個Array來存須要預載的URL,而後實現Finish、Error、SizeChange等經常使用事件,能夠由用戶選擇是順序預載或假併發預載。Jquery的PreLoad能夠用於預載。
獲取屏幕的高度和寬度(屏幕分辨率): window.screen.height/width
獲取屏幕工做區域的高度和寬度(去掉狀態欄): window.screen.availHeight/availWidth
網頁全文的高度和寬度: document.body.scrollHeight/Width
滾動條捲上去的高度和向右卷的寬度: document.body.scrollTop/scrollLeft
網頁可見區域的高度和寬度(不加邊線): document.body.clientHeight/clientWidth
網頁可見區域的高度和寬度(加邊線): document.body.offsetHeight/offsetWidth
document.onreadystatechange頁面加載狀態改變時的事件;
document.readyState返回當前文檔的狀態(uninitialized--還未開始載入;loading--載入中;interactive--已加載,文檔與用戶能夠開始交互;complete--載入完成)
<script type="text/javascript"> //頁面加載狀態改變時的事件 document.onreadystatechange = function () { if(document.readyState == 'complete'){ //判斷頁面加載完成,加載的圖標就隱藏 $(".loading").fadeOut(); } } </script>
在Javascript中,當一個函數被調用時,會建立一個活動記錄(也稱爲執行上下文)。它包含函數在哪裏調用、函數的調用方式、傳入的參數等信息。this就是這個記錄的一個屬性,會在函數執行的過程當中用到。this關鍵字是在運行時進行綁定的,與函數聲明的位置沒有任何關係,它指向什麼徹底取決於函數在哪裏被調用。
函數綁定(默認綁定)
當直接調用函數時就是函數綁定模式。
function fn() { console.log( this.a ); } var a = 2; fn(); // 2 -- fn單獨調用,this引用window
注意:在非嚴格模式下,this將綁定到全局對象window。然而,在嚴格模式下,this將綁定到undefined。
隱式綁定(方法調用)
當函數做爲一個對象的屬性被調用的時候就屬於隱式綁定模式,此時,this指向是調用這個函數的對象。
function test(){ alert(this.x); } var obj = {}; obj.x = 1; obj.m = test; obj.m(); // 1
注意:被隱式綁定的函數會丟失綁定對象,此時,將會應用默認綁定,從而把this綁定到全局對象或undefined上
顯式綁定(硬綁定)
在Javascript中,一般使用call/apply/bind方法來進行顯示綁定。
var x = 0; function test(){ alert(this.x);
}
var obj={}; obj.x = 1; obj.m = test; obj.m.apply(); //0 //apply()的參數爲空時,默認調用全局對象。所以,這時的運行結果爲0,證實this指的是全局對象。若是把最後一行代碼修改成 obj.m.apply(o); //1
new綁定(構造器綁定)
經過new關鍵字調用的函數,屬於new綁定模式。這時this關鍵字指向這個新建立的對象。
function test(){ this.x = 1; } var obj = new test(); alert(obj.x); // 1 //運行結果爲1。爲了代表這時this不是全局對象,我對代碼作一些改變: var x = 2; function test(){ this.x = 1; } var obj = new test(); alert(x); //2
this關鍵字綁定規則的斷定順序
call:
function.call(obj,[param1[,param2[,…[,paramN]]]])
obj:將代替function類裏的this對象
parms:這是一個參數列表
當即執行
apply:
function.apply(obj,args)
obj:將代替function類裏this對象
args:數組,它將做爲參數傳給function
當即執行
bind:
function.bind(obj,arg1,arg2,...)
不會當即執行,而是返回一個新的函數
若是將null或者undefined做爲this的綁定對象傳入call、apply或者bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。
很是常見的作法是使用apply來「展開」一個數組,並看成參數傳入一個函數如:求數組最大最小值,合併數組等,具體用法以下:
var min = Math.min.apply(null, arr); var max = Math.max.apply(null, arr); Array.prototype.push.apply(arrA, arrB);
箭頭函數不使用this的四種綁定規則,而是根據外層(函數或者全局)做用域來決定this的指向。
箭頭函數中的this只和定義它的做用域的this有關,而與在哪裏以及如何調用它無關,同時它的this指向是不能夠改變的。
本部分摘自:https://mp.weixin.qq.com/s/31HlZRug9RjcKBXCtfbBXA
函數式編程的歷史已經很悠久了,可是最近幾年卻頻繁的出如今大衆的視野,不少不支持函數式編程的語言也在積極加入閉包,匿名函數等很是典型的函數式編程特性。大量的前端框架也標榜本身使用了函數式編程的特性,好像一旦跟函數式編程沾邊,就很高大上同樣,並且還有一些專門針對函數式編程的框架和庫,好比:RxJS、cycleJS、ramdaJS、lodashJS、underscoreJS等。函數式編程變得愈來愈流行,掌握這種編程範式對書寫高質量和易於維護的代碼都大有好處。
•純函數
•函數的柯里化(柯里化是一種「預加載」函數的方法,經過傳遞較少的參數,獲得一個已經記住了這些參數的新函數,某種意義上講,這是一種對參數的「緩存」,是一種很是高效的編寫函數的方法。)
•函數組合
•Point Free
•聲明式與命令式代碼
//命令式 let CEOs = []; for (var i = 0; i < companies.length; i++) { CEOs.push(companies[i].CEO) } //聲明式 let CEOs = companies.map(c => c.CEO);
簡單來講,也就是當一個函數的輸出不受外部環境影響,同時也不影響外部環境時,該函數就是純函數,也就是它只關注邏輯運算和數學運算,同一個輸入總獲得同一個輸出。
javascript內置函數有很多純函數,也有很多非純函數。
純函數:
Array.prototype.slice
Array.prototype.map
String.prototype.toUpperCase
非純函數:
Math.random
Date.now
Array.ptototype.splice
調用數組的slice方法每次返回的結果徹底相同,同時數組不會被改變,而調用splice方法每次返回值都不同,同時會改變原數組。
這就是咱們強調使用純函數的緣由,由於純函數相對於非純函數來講,在可緩存性、可移植性、可測試性以及並行計算方面都有着巨大的優點。
把一個函數變純的基本手段是不要依賴系統狀態。
本部分參考連接:https://www.cnblogs.com/fengyuqing/p/functional_programming_1.html http://www.javashuo.com/article/p-axsmkixw-ke.html
parseInt
是ECMAScript核心的一個全局函數,能夠在實現了ECMAScript的宿主環境全局調用。
console.log(parseInt('12')); console.log(parseInt('08')); console.log(parseInt('0x16')); console.log(parseInt('-12')); console.log(parseInt(' -12')); console.log(parseInt(' - 12')); console.log(parseInt('124ref')); console.log(parseInt('ref'));
parseInt(string, [int radix])
第二個形參是能夠忽略的,忽略時默認賦值爲10也就是十進制。
radix就是指定第一個形參的進制類型,而後根據這個進制類型再轉換爲十進制整數
radix形參沒指定的時候是10,有效範圍:[2, 36]和特殊值0
1. 將第一個形參轉換爲字符串
2. 識別string轉換是否有code unit,若是有 -, -標記爲負數,0x或0X則把radix賦值爲16
3. radix形參(int類型)是否存在,存在則從新賦值(會對實參進行Int32轉化,沒法轉換成int類型則不會從新賦值radix)
4. radix爲0,則設置radix爲默認值10
5. 若是radix爲1,或者大於等於37,parseInt直接返回NaN
6. 若是radix爲[2, 36]時則表明,string參數分別是二進制,三進制(若是有得話~)…三十六進制類型
7. 而後對string進行的radix進制進行十進制轉換,例如,按二進制對string來進行十進制轉換
['1', '2', '3'].map(parseInt) //[1, NaN, NaN] //內部執行的剖析 (function (){ var ret = ['1', '2', '3'].map((value, index)=>{ console.log(value, index); return parseInt(value, index); }); console.log(ret); })(); //所以,其實是 parseInt('1', 0); parseInt('2', 1); parseInt('3', 2);
parseInt('13', 2)
,這個結果是……1
,由於string參數若是最開始的code符合radix進制的話是能夠進行解析轉換的,正如這裏’1’是符合二進制的,’3’是不符合二進制的,但1
處於優先位置,因此能夠進行轉換解析,而3
被無情地忽略~
function l(obj) { return console.log(obj) } function _parseInt(str,radix){ var res = 0; if(typeof str !="string" && typeof str !="number"){ return NaN; } str =String(str).trim().split(".")[0]; // l(str) let len = str.length; if(!len){ return NaN; } if(!radix){ return radix = 10; } if(typeof radix !=="number" || radix < 2 || radix >36){ return NaN; } for(let i = 0; i < len; i++){ let arr = str.split(""); l(arr instanceof Array) l(typeof arr) res += Math.floor(arr[i])*Math.pow(radix,i) } l(res); } _parseInt("654646",10)
同源策略是瀏覽器的一個安全功能,不一樣源的客戶端腳本在沒有明確受權的狀況下,不能讀寫對方資源。只有同一個源的腳本賦予dom、讀寫cookie、session、ajax等操做的權限。url由協議、域名、端口和路徑組成、若是兩個url的協議、域名和端口相同,則這兩個url是同源的。限制來源不用源的「document」,對當前的「document」讀取或設置某些屬性。在不受同源策略限制,帶有「src」屬性的標籤加載是,其實是由遊覽器發起一次GET請求,不一樣於XMLHTTPRequest,它們經過src屬性加載的資源。但遊覽器限制了JavaScript的權限,使其不能讀,寫其中返回的內容。
若是沒有同源策略,不一樣源的數據和資源(如HTTP頭、Cookie、DOM、localStorage等)就能相互隨意訪問,根本沒有隱私和安全可言。爲了安全起見和資源的有效管理,瀏覽器固然要採用這種策略。
同源策略是一種約定,它是瀏覽器最核心和最基本的安全功能,能夠用於隔離潛在惡意文件,若是沒有了同源策略,瀏覽器的正常使用將受到影響。
瀏覽器採用同源策略,禁止頁面加載或執行與自身不一樣源的任何腳本。若是沒有同源策略,那麼惡意網頁能夠讀取銀行網站、網上商城等裏面的用戶信息,甚至篡改帳號密碼等。因此全部支持JavaScript的瀏覽器都採用了同源策略。
ES6有一個方法來判斷兩個對象是否相等 console.log(Object.is(a,b)) ,可是這個相等,和咱們平時要的相等可能不同,這個方法判斷的是a和b是否是同一個指針的對象。
var a = { id:1 }; var b = a; console.log(Object.is(a,b)); //true //當咱們只須要兩個對象的內容相同的時候,他就沒效果了 var a = { id:1 }; var b = { id:1 } console.log(Object.is(a,b)); //false
思路:只要兩個對象的名和鍵值都相同。那麼兩個對象的內容就相同了(考慮若是鍵值也是對象的狀況——用遞歸,遞歸的時候要判斷prop是否是Object)
1.用Object.getOwnPropertyNames拿到對象的因此鍵名數組
2.比對鍵名數組的長度是否相等。否=>false。真=>3
3.比對鍵名對應的鍵值是否相等
function isObjectValueEqual(a, b) { var aProps = Object.getOwnPropertyNames(a); var bProps = Object.getOwnPropertyNames(b); if (aProps.length != bProps.length) { return false; } for (var i = 0; i < aProps.length; i++) { var propName = aProps[i] var propA = a[propName] var propB = b[propName] if (propA !== propB) { if ((typeof (propA) === 'object')) { if (this.isObjectValueEqual(propA, propB)) { return true } else { return false } } else { return false } } else { return false } } return true; } var a = { id:1, name:2, c:{ age:3 } }; var b = { id:1, name:2, c:{ age:3 } } console.log(isObjectValueEqual(a,b));//true
本部分摘自:https://www.jianshu.com/p/7407bd65b15d
這是一種被全部瀏覽器都支持的事件模型,對於原始事件而言,沒有事件流,事件一旦發生將立刻進行處理,有兩種方式能夠實現原始事件:
(1)在html代碼中直接指定屬性值:<button id="demo" type="button" onclick="doSomeTing()" />
(2)在js代碼中爲 document.getElementsById("demo").onclick = doSomeTing()
優勢:全部瀏覽器都兼容
缺點:1)邏輯與顯示沒有分離;2)相同事件的監聽函數只能綁定一個,後綁定的會覆蓋掉前面的,如:a.onclick = func1; a.onclick = func2;將只會執行func2中的內容。3)沒法經過事件的冒泡、委託等機制完成更多事情。
由於這些缺點,雖然原始事件類型兼容全部瀏覽器,但仍不推薦使用。
此模型是W3C制定的標準模型,現代瀏覽器(IE6~8除外)都已經遵循這個規範。W3C制定的事件模型中,一次事件的發生包含三個過程:(1).事件捕獲階段,(2).事件目標階段,(3).事件冒泡階段
事件捕獲:當某個元素觸發某個事件(如onclick),頂層對象document就會發出一個事件流,隨着DOM樹的節點向目標元素節點流去,直到到達事件真正發生的目標元素。在這個過程當中,事件相應的監聽函數是不會被觸發的。
事件目標:當到達目標元素以後,執行目標元素該事件相應的處理函數。若是沒有綁定監聽函數,那就不執行。
事件冒泡:從目標元素開始,往頂層元素傳播。途中若是有節點綁定了相應的事件處理函數,這些函數都會被一次觸發。
全部的事件類型都會經歷事件捕獲可是隻有部分事件會經歷事件冒泡階段,例如submit事件就不會被冒泡。
事件的傳播是能夠阻止的:
• 在W3c中,使用stopPropagation()方法
• 在IE下設置cancelBubble = true;
在捕獲的過程當中stopPropagation();後,後面的冒泡過程就不會發生了。
標準的事件監聽器該如何綁定:
addEventListener("eventType","handler","true|false");其中eventType指事件類型,注意不要加‘on’前綴,與IE下不一樣。第二個參數是處理函數,第三個即用來指定是否在捕獲階段進行處理,通常設爲false來與IE保持一致(默認設置),除非你有特殊的邏輯需求。監聽器的解除也相似:removeEventListner("eventType","handler","true!false");
IE不把該對象傳入事件處理函數,因爲在任意時刻只會存在一個事件,因此IE把它做爲全局對象window的一個屬性,爲求證其真僞,使用IE8執行代碼alert(window.event),結果彈出是null,說明該屬性已經定義,只是值爲null(與undefined不一樣)。難道這個全局對象的屬性是在監聽函數裏才加的?因而執行下面代碼:
window.onload = function (){alert(window.event);}
setTimeout(function(){alert(window.event);},2000);
結果第一次彈出【object event】,兩秒後彈出依然是null。因而可知IE是將event對象在處理函數中設爲window的屬性,一旦函數執行結束,便被置爲null了。IE的事件模型只有兩步,先執行元素的監聽函數,而後事件沿着父節點一直冒泡到document。冒泡已經講解過了,這裏不重複。IE模型下的事件監聽方式也挺獨特,綁定監聽函數的方法是:attachEvent( "eventType","handler"),其中evetType爲事件的類型,如onclick,注意要加’on’。解除事件監聽器的方法是 detachEvent("eventType","handler" )
IE的事件模型已經能夠解決原始模型的三個缺點,但其本身的缺點就是兼容性,只有IE系列瀏覽器才能夠這樣寫。
以上就是3種事件模型,在咱們寫代碼的時候,爲了兼容ie,一般使用如下寫法:
var demo = document.getElementById('demo'); if(demo.attachEvent){ demo.attachEvent('onclick',func); }else{ demo.addEventListener('click',func,false); }
事件被封裝成一個event對象,包含了該事件發生時的全部相關信息(event的屬性)以及能夠對事件進行的操做(event的方法)。
x/y與clientX/clientY值同樣,表示距瀏覽器可視區域(工具欄除外區域)左/上的距離;
pageX/pageY,距頁面左/上的距離,它與clientX/clientY的區別是不隨滾動條的位置變化;
screenX/screenY,距計算機顯示器左/上的距離,拖動你的瀏覽器窗口位置能夠看到變化;
layerX/layerY與offsetX/offsetY值同樣,表示距有定位屬性的父元素左/上的距離。
target:發生事件的節點;
currentTarget:當前正在處理的事件的節點,在事件捕獲或冒泡階段;
timeStamp:事件發生的時間,時間戳。
bubbles:事件是否冒泡。
cancelable:事件是否能夠用preventDefault()方法來取消默認的動做;
keyCode:按下的鍵的值;
event. preventDefault()//阻止元素默認的行爲,如連接的跳轉、表單的提交;
event. stopPropagation()//阻止事件冒泡
event.initEvent()//初始化新事件對象的屬性,自定義事件會用,不經常使用
event. stopImmediatePropagation()//能夠阻止掉同一事件的其餘優先級較低的偵聽器的處理(這貨表示沒用過,優先級就不說明了,谷歌或者問度娘吧。)
event.target與event.currentTarget他們有什麼不一樣?
target在事件流的目標階段;currentTarget在事件流的捕獲,目標及冒泡階段。只有當事件流處在目標階段的時候,兩個的指向纔是同樣的, 而當處於捕獲和冒泡階段的時候,target指向被單擊的對象而currentTarget指向當前事件活動的對象(通常爲父級)。
本部分摘自:http://www.javashuo.com/article/p-ghctezii-k.html
一、當 onload
事件觸發時,頁面上全部的DOM,樣式表,腳本,圖片,flash都已經加載完成了。
二、當 DOMContentLoaded
事件觸發時,僅當DOM加載完成,不包括樣式表,圖片,flash。
onload事件是DOM事件,onDOMContentLoaded是HTML5事件。
onload事件會被樣式表、圖像和子框架阻塞,而onDOMContentLoaded不會。
當加載的腳本內容並不包含當即執行DOM操做時,使用onDOMContentLoaded事件是個更好的選擇,會比onload事件執行時間更早。
1. for…in 語句以原始插入順序迭代對象的可枚舉屬性。
2. for…of 語句遍歷可迭代對象定義要迭代的數據。
Object.prototype.objCustom = function() {}; Array.prototype.arrCustom = function() {}; let iterable = [3, 5, 7]; iterable.foo = 'hello'; //for in 會繼承 for (let i in iterable) { console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom" } for (let i in iterable) { if (iterable.hasOwnProperty(i)) { console.log(i); // logs 0, 1, 2, "foo" } } // for of for (let i of iterable) { console.log(i); // logs 3, 5, 7 }
for...of循環是ES6引入的新的語法。
for...in遍歷拿到的x是鍵(下標)。而for...of遍歷拿到的x是值,但在對象中會提示不是一個迭代器報錯。
let x; let a = ['A','B','C']; let b = {name: '劉德華',age: '18'}; console.log(a.length); for(x of a){ console.log(x); //A,B,C } for(x in a){ console.log(x+':'+a[x]); //0:A,1:B,2:C } /*for(x of b){ console.log(x); //報錯 }*/ for(x in b){ console.log(x); //name,age } a.name = "Hello"; for(x in a){ console.log(x); //0,1,2,name } console.log(a.length); //3
for...in因爲歷史遺留問題,它遍歷的其實是對象的屬性名稱,一個Array數據也是一個對象,數組中的每一個元素的索引被視爲屬性名稱。
因此咱們能夠看到使用for...in循環Array數組時,拿到的實際上是每一個元素的索引。以下,把name包括在內,可是Array的length屬性卻不包括在內,for...of循環則徹底修復了這些問題,它只循環集合自己的元素。
將一個低階函數轉換爲高階函數的過程就叫柯里化。好比對於加法操做: var add = (x, y) => x + y ,咱們能夠這樣柯里化
function curryingAdd(x) { return function (y) { return x + y } }
只傳遞給函數一部分參數來調用它,讓它返回一個函數去處理剩下的參數
// 經典面試題,實現一個add方法,使計算結果可以知足以下預期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() { // 第一次執行時,定義一個數組專門用來存儲全部的參數 var _args = Array.prototype.slice.call(arguments); // 在內部聲明一個函數,利用閉包的特性保存_args並收集全部的參數值 var _adder = function() { _args.push(...arguments); return _adder; }; // 利用toString隱式轉換的特性,當最後執行時隱式轉換,並計算最終的值返回 _adder.toString = function () { return _args.reduce(function (a, b) { return a + b; }); } return _adder; }
call,apply,bind 三者用法和區別:角度可爲參數、綁定規則(顯示綁定和強綁定),運行效率、運行狀況。
//bind的實現就是柯里化 Function.prototype.bind = function (context) { var _this = this var args = Array.prototype.slice.call(arguments, 1) return function() { return _this.apply(context, args) } }
Function.prototype.myCall = function(context = window, ...rest) { context.fn = this; //此處this是指調用myCall的function let result = context.fn(...rest); //將this指向銷燬 delete context.fn; return result; };
Function.prototype.myApply = function(context = window, params = []) { context.fn = this; //此處this是指調用myCall的function let result if (params.length) { result = context.fn(...params) }else { result = context.fn() } //將this指向銷燬 delete context.fn; return result; };
Function.prototype.myBind = function(thisArg) { if (typeof this !== 'function') { return; } var _self = this; var args = Array.prototype.slice.call(arguments, 1) var fnBound = function () { // 檢測 New // 若是當前函數的this指向的是構造函數中的this 則斷定爲new 操做 var _this = this instanceof _self ? this : thisArg; return _self.apply(_this, args.concat(Array.prototype.slice.call(arguments))); } // 爲了完成 new操做 // 還須要作一件事情 執行原型 連接 (思考題,爲何? fnBound.prototype = this.prototype; return fnBound; }
async/await更加語義化,async 是「異步」的簡寫,async function 用於申明一個 function 是異步的; await,能夠認爲是async wait的簡寫, 用於等待一個異步方法執行完成;
async/await是一個用同步思惟解決異步問題的方案(等結果出來以後,代碼纔會繼續往下執行)
能夠經過多層 async function 的同步寫法代替傳統的callback嵌套
自動將常規函數轉換成Promise,返回值也是一個Promise對象
只有async函數內部的異步操做執行完,纔會執行then方法指定的回調函數
異步函數內部能夠使用await
await 放置在Promise調用以前,await 強制後面點代碼等待,直到Promise對象resolve,獲得resolve的值做爲await表達式的運算結果
await只能在async函數內部使用,用在普通函數裏就會報錯
const timeoutFn = function(timeout){ return new Promise(function(resolve){ return setTimeout(resolve, timeout); }); } async function fn(){ await timeoutFn(1000); await timeoutFn(2000); return '完成'; } fn().then(success => console.log(success));
你的代碼在頁面加載完成以後,不得不執行一些設置工做,好比時間處理器,建立對象等等。全部的這些工做只須要執行一次,好比只須要顯示一個時間。可是這些代碼也須要一些臨時的變量,可是初始化過程結束以後,就不再會被用到,若是將這些變量做爲全局變量,不是一個好的注意,咱們能夠用當即執行函數——去將咱們全部的代碼包裹在它的局部做用域中,不會讓任何變量泄露成全局變量。
單例模式的定義:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。實現的方法爲先判斷實例存在與否,若是存在則直接返回,若是不存在就建立了再返回,這就確保了一個類只有一個實例對象。
適用場景:一個單一對象。好比:彈窗,不管點擊多少次,彈窗只應該被建立一次。
// 單例模式 var Singleton = function(name){ this.name = name; }; Singleton.prototype.getName = function(){ return this.name; } // 獲取實例對象,代理實現單例模式 var getInstance = (function() { var instance = null; return function(name) { if(!instance) { instance = new Singleton(name); } return instance; } })(); // 測試單例模式的實例 var a = getInstance("aa"); var b = getInstance("bb");
// 實現單例模式彈窗 var createWindow = (function(){ var div; return function(){ if(!div) { div = document.createElement("div"); div.innerHTML = "我是彈窗內容"; div.style.display = 'none'; document.body.appendChild(div); } return div; } })(); document.getElementById("Id").onclick = function(){ // 點擊後先建立一個div元素 var win = createWindow(); win.style.display = "block"; }
策略模式的定義:定義一系列的算法,把他們一個個封裝起來,而且使他們能夠相互替換。
/*策略類*/ var levelOBJ = { "A": function(money) { return money * 4; }, "B" : function(money) { return money * 3; }, "C" : function(money) { return money * 2; } }; /*環境類*/ var calculateBouns =function(level,money) { return levelOBJ[level](money); }; console.log(calculateBouns('A',10000)); // 40000
代理模式的定義:爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。
經常使用的虛擬代理形式:某一個花銷很大的操做,能夠經過虛擬代理的方式延遲到這種須要它的時候纔去建立(例:使用虛擬代理實現圖片懶加載)
圖片懶加載的方式:先經過一張loading圖佔位,而後經過異步的方式加載圖片,等圖片加載好了再把完成的圖片加載到img標籤裏面。
var imgFunc = (function() { var imgNode = document.createElement('img'); document.body.appendChild(imgNode); return { setSrc: function(src) { imgNode.src = src; } } })(); var proxyImage = (function() { var img = new Image(); img.onload = function() { imgFunc.setSrc(this.src); } return { setSrc: function(src) { imgFunc.setSrc('./loading,gif'); img.src = src; } } })(); proxyImage.setSrc('./pic.png');
裝飾者模式的定義:在不改變對象自身的基礎上,在程序運行期間給對象動態地添加方法。
例如:現有4種型號的自行車分別被定義成一個單獨的類,若是給每輛自行車都加上前燈、尾燈、鈴鐺這3個配件,若是用類繼承的方式,須要建立4*3=12個子類。但若是經過裝飾者模式,只須要建立3個類。
裝飾者模式適用的場景:原有方法維持不變,在原有方法上再掛載其餘方法來知足現有需求;函數的解耦,將函數拆分紅多個可複用的函數,再將拆分出來的函數掛載到某個函數上,實現相同的效果但加強了複用性。
//用AOP裝飾函數實現裝飾者模式 Function.prototype.before = function(beforefn) { var self = this; //保存原函數引用 return function(){ //返回包含了原函數和新函數的 '代理函數' beforefn.apply(this, arguments); //執行新函數,修正this return self.apply(this,arguments); //執行原函數 } } Function.prototype.after = function(afterfn) { var self = this; return function(){ var ret = self.apply(this,arguments); afterfn.apply(this, arguments); return ret; } } var func = function() { console.log('2'); } //func1和func3爲掛載函數 var func1 = function() { console.log('1'); } var func3 = function() { console.log('3'); } func = func.before(func1).after(func3); func();
本部分摘自:http://www.javashuo.com/article/p-wzevjtae-dm.html http://www.javashuo.com/article/p-ovztdblg-gm.html
不容易管理
。
用戶體驗度差
。
不利於搜索引擎優化
。
設備兼容性
差。
增長服務器的http請求
,對於大型網站是不可取的。
如今基本上都是用Ajax來代替iframe,因此iframe已經漸漸的退出了前端開發
。
shift:刪除原數組第一項,並返回刪除元素的值;若是數組爲空則返回undefined
unshift:將參數添加到原數組開頭,並返回數組的長度
pop:刪除原數組最後一項,並返回刪除元素的值;若是數組爲空則返回undefined
push:將參數添加到原數組末尾,並返回數組的長度
concat:返回一個新數組,是將參數添加到原數組中構成的
splice(start,deleteCount,val1,val2,...):從start位置開始刪除deleteCount項,並從該位置起插入val1,val2,..會改變原數組
reverse:將數組反序
sort(orderfunction):按指定的參數對數組進行排序
slice(start,end):返回從原數組中指定開始下標到結束下標之間的項組成的新數組
join(separator):將數組的元素組起一個字符串,以separator爲分隔符,省略的話則用默認用逗號爲分隔符
var a = [1,2,3,4,5]; var b = a.shift(); //a:[2,3,4,5] b:1 var a = [1,2,3,4,5]; var b = a.unshift(-2,-1); //a:[-2,-1,1,2,3,4,5] b:7 var a = [1,2,3,4,5]; var b = a.pop(); //a:[1,2,3,4] b:5 var a = [1,2,3,4,5]; var b = a.push(6,7); //a:[1,2,3,4,5,6,7] b:7 var a = [1,2,3,4,5]; var b = a.concat(6,7); //a:[1,2,3,4,5] b:[1,2,3,4,5,6,7] var a = [1,2,3,4,5]; var b = a.splice(2,2,7,8,9); //a:[1,2,7,8,9,5] b:[3,4] var b = a.splice(0,1); //同shift a.splice(0,0,-2,-1); var b = a.length; //同unshift var b = a.splice(a.length-1,1); //同pop a.splice(a.length,0,6,7); var b = a.length; //同push var a = [1,2,3,4,5]; var b = a.reverse(); //a:[5,4,3,2,1] b:[5,4,3,2,1] var a = [1,2,3,4,5]; var b = a.sort(); //a:[1,2,3,4,5] b:[1,2,3,4,5] var a = [1,2,3,4,5]; var b = a.slice(2,5); //a:[1,2,3,4,5] b:[3,4,5] var a = [1,2,3,4,5]; var b = a.join("|"); //a:[1,2,3,4,5] b:"1|2|3|4|5"
for循環
使用臨時變量,將長度緩存起來,避免重複獲取數組長度,當數組較大時優化效果纔會比較明顯。
foreach循環
遍歷數組中的每一項,沒有返回值,對原數組沒有影響,不支持IE
//1 沒有返回值 arr.forEach((item,index,array)=>{ //執行代碼 }) //參數:value數組中的當前項, index當前項的索引, array原始數組; //數組中有幾項,那麼傳遞進去的匿名回調函數就須要執行幾回; //基本用法 var ary = ["JavaScript", "Java", "CoffeeScript", "TypeScript"]; ary.forEach(function(value, index, _ary) { console.log(index + ": " + value); return false; });
使用some或every能夠中斷循環
//使用some函數 var ary = ["JavaScript", "Java", "CoffeeScript", "TypeScript"]; ary.some(function (value, index, _ary) { console.log(index + ": " + value); return value === "CoffeeScript"; }); // logs: //0: JavaScript //1: Java //2: CoffeeScript //使用every var ary = ["JavaScript", "Java", "CoffeeScript", "TypeScript"]; ary.every(function(value, index, _ary) { console.log(index + ": " + value); return value.indexOf("Script") > -1; }); // logs: //0: JavaScript //1: Java
補充:如何中斷foreach循環呢?
//循環外使用try.. catch,當須要中斷時throw 一個異常,而後catch進行捕獲; var BreakException = {}; try { [1, 2, 3].forEach(function(el) { console.log(el); if (el === 2) throw BreakException; }); } catch (e) { if (e !== BreakException) throw e; }
map循環
有返回值,能夠return出來
map的回調函數中支持return返回值;return的是啥,至關於把數組中的這一項變爲啥(並不影響原來的數組,只是至關於把原數組克隆一份,把克隆的這一份的數組中的對應項改變了);
var ary = [12,23,24,42,1]; var res = ary.map(function (item,index,ary ) { return item*10; }) console.log(res);//-->[120,230,240,420,10]; 原數組拷貝了一份,並進行了修改 console.log(ary);//-->[12,23,24,42,1]; 原數組並未發生變化
for of遍歷
能夠正確響應break、continue和return語句
for (var value of myArray) { console.log(value); }
//基本用法 let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; for (let el of arr) { console.log(el); if (el === 5) { break; } }
filter遍歷
不會改變原始數組,返回新數組
var arr = [ { id: 1, text: 'aa', done: true }, { id: 2, text: 'bb', done: false } ] console.log(arr.filter(item => item.done)) //es5寫法 arr.filter(function (item) { return item.done; }); var arr = [73,84,56, 22,100] var newArr = arr.filter(item => item>80) //獲得新數組 [84, 100] console.log(newArr,arr)
reduce
reduce()
方法接收一個函數做爲累加器(accumulator),數組中的每一個值(從左到右)開始縮減,最終爲一個值。
var total = [0,1,2,3,4].reduce((a, b)=>a + b); //10 //reduce接受一個函數,函數有四個參數,分別是:上一次的值,當前值,當前值的索引,數組 [0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array){ return previousValue + currentValue; }); //reduce還有第二個參數,咱們能夠把這個參數做爲第一次調用callback時的第一個參數,上面這個例子由於沒有第二個參數,因此直接從數組的第二項開始,若是咱們給了第二個參數爲5,那麼結果就是這樣的: [0, 1, 2, 3, 4].reduce(function(previousValue, currentValue, index, array){ return previousValue + currentValue; },5);
find
find()方法返回數組中符合測試函數條件的第一個元素。不然返回undefined
stu.find((element) => (element.name == '李四')) //es5 function getStu(element){ return element.name == '李四' } stu.find(getStu)
keys,values,entries
ES6 提供三個新的方法 —— entries(),keys()和values() —— 用於遍歷數組。它們都返回一個遍歷器對象,能夠用for...of循環進行遍歷,惟一的區別是keys()是對鍵名的遍歷、values()是對鍵值的遍歷,entries()是對鍵值對的遍歷
for (let index of ['a', 'b'].keys()) { console.log(index); } // 0 // 1 for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); } // 0 "a" // 1 "b"
本部分參考連接:http://www.javashuo.com/article/p-qezqemuj-ek.html http://www.javashuo.com/article/p-erqmascr-ee.html
for...in...
Object.keys(obj)
Object.values(obj)
Object.getOwnPropertyNames(obj)
const obj = { id:1, name:'zhangsan', age:18 } for(let key in obj){ console.log(key + '---' + obj[key]) } console.log(Object.keys(obj)) console.log(Object.values(obj)) Object.getOwnPropertyNames(obj).forEach(function(key){ console.log(key+ '---'+obj[key]) })
什麼是垃圾:通常來講沒有被引用的對象就是垃圾,就是要被清除, 有個例外若是幾個對象引用造成一個環,互相引用,但根訪問不到它們,這幾個對象也是垃圾,也要被清除。
JavaScript 中的內存管理是自動執行的,並且是不可見的。咱們建立基本類型、對象、函數……全部這些都須要內存。
JavaScript 中內存管理的主要概念是可達性。
簡單地說,「可達性」 值就是那些以某種方式可訪問或可用的值,它們被保證存儲在內存中。
1. 有一組基本的固有可達值,因爲顯而易見的緣由沒法刪除。例如:
這些值稱爲根。
2. 若是引用或引用鏈能夠從根訪問任何其餘值,則認爲該值是可訪問的。
例如,若是局部變量中有對象,而且該對象具備引用另外一個對象的屬性,則該對象被視爲可達性, 它引用的那些也是能夠訪問的,詳細的例子以下。
JavaScript 引擎中有一個後臺進程稱爲垃圾回收器,它監視全部對象,並刪除那些不可訪問的對象。
// user 具備對象的引用 let user = { name: "John" }; user = null; //此時,user 的值被覆蓋,則引用丟失 //如今 John 變成不可達的狀態,沒有辦法訪問它,沒有對它的引用。垃圾回收器將丟棄 John 數據並釋放內存。
JS環境中分配的內存通常有以下生命週期:
基本的垃圾回收算法稱爲「標記-清除」,按期執行如下「垃圾回收」步驟:
一些優化:
本部分摘自:https://segmentfault.com/a/1190000018605776?utm_source=tag-newest
Javascript的事件分爲同步任務和異步任務.
遇到同步任務就放在執行棧中執行.
遇到異步任務就放到任務隊列之中,等到執行棧執行完畢以後再去執行任務隊列之中的事件。
Javascript 有一個 主線程(main thread)和 調用棧(call-stack),全部的代碼都要經過函數,放到調用棧(也被稱爲執行棧)中的任務等待主線程執行。
JS調用棧採用的是後進先出的規則,當函數執行的時候,會被添加到棧的頂部,當執行棧執行完成後,就會從棧頂移出,直到棧內被清空。
MDN的解釋: Web 提供了各類各樣的 API 來完成各類的任務。這些 API 能夠用 JavaScript 來訪問,令你能夠作不少事兒,小到對任意 window 或者 element作小幅調整,大到使用諸如 WebGL 和 Web Audio 的 API 來生成複雜的圖形和音效。
總結: 就是瀏覽器提供一些接口,讓JavaScript能夠調用,這樣就能夠把任務甩給瀏覽器了,這樣就能夠實現異步了!
"任務隊列"是一個先進先出的數據結構,排在前面的事件,優先被主線程讀取。主線程的讀取過程基本上是自動的,只要執行棧一清空,"任務隊列"上第一位的事件就自動進入主線程。可是,若是存在"定時器",主線程首先要檢查一下執行時間,某些事件只有到了規定的時間,才能返回主線程。
Javascript單線程任務被分爲同步任務和異步任務.
宏任務(MacroTask)和 微任務(MicroTask):在JavaScript
中,任務被分爲兩種,一種宏任務(MacroTask
)也叫Task
,一種叫微任務(MicroTask
)。
宏任務(MacroTask):script(總體代碼)
、setTimeout
、setInterval
、setImmediate
(瀏覽器暫時不支持,只有IE10支持,具體可見MDN
)、I/O
、UI Rendering
。
微任務(MicroTask):Process.nextTick(Node獨有)
、Promise
、Object.observe(廢棄)
、MutationObserver
(具體使用方式查看這裏)
注意:只要主線程空了,就會去讀取"任務隊列",這就是JavaScript的運行機制。這個過程會不斷重複。
console.log('script start') async function async1() { await async2() console.log('async1 end') } async function async2() { console.log('async2 end') } async1() setTimeout(function() { console.log('setTimeout') }, 0) new Promise(resolve => { console.log('Promise') resolve() }) .then(function() { console.log('promise1') }) .then(function() { console.log('promise2') }) console.log('script end') //script start //async2 end //Promise //script end //async1 end //promise1 //promise2 //undefined //setTimeout
script start
,調用async1()
時,返回一個Promise
,因此打印出來async2 end
。await
,會新產生一個promise
,但這個過程自己是異步的,因此該await
後面不會當即調用。Promise
和script end
,將then
函數放入微任務隊列中等待執行。null
,而後按照先入先出規則,依次執行。promise1
,此時then
的回調函數返回undefinde
,此時又有then
的鏈式調用,又放入微任務隊列中,再次打印promise2
。await
的位置執行返回的 Promise
的 resolve
函數,這又會把 resolve
丟到微任務隊列中,打印async1 end
。setTimeout
。執行完主執行線程中的任務。
取出Microtask Queue中任務執行直到清空。
取出Macrotask Queue中一個任務執行。
取出Microtask Queue中任務執行直到清空。
重複3和4。
即爲同步完成,一個宏任務,全部微任務,一個宏任務,全部微任務......
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end'); //結果 //script start //script end //promise1 //promise2 //setTimeout
解析:一開始task隊列中只有script,則script中全部函數放入函數執行棧執行,代碼按順序執行。
接着遇到了setTimeout,它的做用是0ms後將回調函數放入task隊列中,也就是說這個函數將在下一個事件循環中執行(注意這時候setTimeout執行完畢就返回了)。
接着遇到了Promise,按照前面所述Promise屬於microtask,因此第一個.then()會放入microtask隊列。
當全部script代碼執行完畢後,此時函數執行棧爲空。開始檢查microtask隊列,此時隊列不爲空,執行.then()的回調函數輸出'promise1',因爲.then()返回的依然是promise,因此第二個.then()會放入microtask隊列繼續執行,輸出'promise2'。
此時microtask隊列爲空了,進入下一個事件循環,檢查task隊列發現了setTimeout的回調函數,當即執行回調函數輸出'setTimeout',代碼執行完畢。
以上即是瀏覽器事件循環的過程
事件循環能讓 Node.js 執行非阻塞 I/O 操做,儘管JavaScript事實上是單線程的,經過在可能的狀況下把操做交給操做系統內核來實現。
因爲大多數現代系統內核是多線程的,內核能夠處理後臺執行的多個操做。當其中一個操做完成的時候,內核告訴 Node.js,相應的回調就被添加到輪詢隊列(poll queue)並最終獲得執行。
在node中事件每一輪循環按照順序分爲6個階段,來自libuv的實現:
每個階段都有一個裝有callbacks的fifo queue(隊列),當event loop運行到一個指定階段時,node將執行該階段的fifo queue(隊列),當隊列callback執行完或者執行callbacks數量超過該階段的上限時,event loop會轉入下一下階段.
上面六個階段都不包括 process.nextTick(),process.nextTick不是基於libuv事件機制的,而timers一系列的api所有是基於libuv開放出來的api實現的。
定時器的用途是讓指定的回調函數在某個閾值後會被執行,具體的執行時間並不必定是那個精確的閾值。定時器的回調會在制定的時間事後儘快獲得執行,然而,操做系統的計劃或者其餘回調的執行可能會延遲該回調的執行。
輪詢階段有兩個主要功能:
1,執行已經到時的定時器腳本
2,處理輪詢隊列中的事件
當事件循環進入到輪詢階段卻沒有發現定時器時:
若是輪詢隊列非空,事件循環會迭代回調隊列並同步執行回調,直到隊列空了或者達到了上限(前文說過的根據操做系統的不一樣而設定的上限)。
若是輪詢隊列是空的:
若是有setImmediate()定義了回調,那麼事件循環會終止輪詢階段並進入檢查階段去執行定時器回調;
若是沒有setImmediate(),事件回調會等待回調被加入隊列並當即執行。
一旦輪詢隊列空了,事件循環會查找已經到時的定時器。若是找到了,事件循環就回到定時器階段去執行回調。
這個階段執行一些諸如TCP錯誤之類的系統操做的回調。例如,若是一個TCP socket 在嘗試鏈接時收到了 ECONNREFUSED錯誤,某些 *nix 系統會等着報告這個錯誤。這個就會被排到本階段的隊列中。
這個階段容許回調函數在輪詢階段完成後當即執行。若是輪詢階段空閒了,而且有回調已經被 setImmediate() 加入隊列,事件循環會進入檢查階段而不是在輪詢階段等待。
setImmediate() 是個特殊的定時器,在事件循環中一個單獨的階段運行。它使用libuv的API 來使得回調函數在輪詢階段完成後執行。
若是一個 socket 或句柄(handle)被忽然關閉(is closed abruptly),例如 socket.destroy(), 'close' 事件會被髮出到這個階段。不然這種事件會經過 process.nextTick() 被髮出。
兩者很是類似,可是兩者區別取決於他們何時被調用.
setImmediate 設計在poll階段完成時執行,即check階段;
setTimeout 設計在poll階段爲空閒時,且設定時間到達後執行;但其在timer階段執行
其兩者的調用順序取決於當前event loop的上下文,若是他們在異步i/o callback以外調用,其執行前後順序是不肯定的
setTimeout(function timeout () { console.log('timeout'); },0); setImmediate(function immediate () { console.log('immediate'); }); $ node timeout_vs_immediate.js timeout immediate $ node timeout_vs_immediate.js immediate timeout
爲何結果不肯定呢?
解釋:setTimeout/setInterval 的第二個參數取值範圍是:[1, 2^31 - 1],若是超過這個範圍則會初始化爲 1,即 setTimeout(fn, 0) === setTimeout(fn, 1)。咱們知道 setTimeout 的回調函數在 timer 階段執行,setImmediate 的回調函數在 check 階段執行,event loop 的開始會先檢查 timer 階段,可是在開始以前到 timer 階段會消耗必定時間,因此就會出現兩種狀況:
timer 前的準備時間超過 1ms,知足 loop->time >= 1,則執行 timer 階段(setTimeout)的回調函數
timer 前的準備時間小於 1ms,則先執行 check 階段(setImmediate)的回調函數,下一次 event loop 執行 timer 階段(setTimeout)的回調函數
再看個例子:
setTimeout(() => { console.log('setTimeout') }, 0) setImmediate(() => { console.log('setImmediate') }) const start = Date.now() while (Date.now() - start < 10); //結果 //setTimeout //setImmediate
本部分摘自:http://www.javashuo.com/article/p-xacejgab-kn.html 淺析Nodejs Event Loop
toLocalString()
\ 作爲轉意,即一般在"\"後面的字符不按原來意義解釋,如/b/匹配字符"b",當b前面加了反斜杆後/\b/,轉意爲匹配一個單詞的邊界。
-或-
對正則表達式功能字符的還原,如"*"匹配它前面元字符0次或屢次,/a*/將匹配a,aa,aaa,加了"\"後,/a\*/將只匹配"a*"。
^ 匹配一個輸入或一行的開頭,/^a/匹配"an A",而不匹配"An a"
$ 匹配一個輸入或一行的結尾,/a$/匹配"An a",而不匹配"an A"
* 匹配前面元字符0次或屢次,/ba*/將匹配b,ba,baa,baaa
+ 匹配前面元字符1次或屢次,/ba*/將匹配ba,baa,baaa
? 匹配前面元字符0次或1次,/ba*/將匹配b,ba
(x) 匹配x保存x在名爲$1...$9的變量中
x|y 匹配x或y
{n} 精確匹配n次
{n,} 匹配n次以上
{n,m} 匹配n-m次
[xyz] 字符集(character set),匹配這個集合中的任一一個字符(或元字符)
[^xyz] 不匹配這個集合中的任何一個字符
[\b] 匹配一個退格符
\b 匹配一個單詞的邊界
\B 匹配一個單詞的非邊界
\cX 這兒,X是一個控制符,/\cM/匹配Ctrl-M
\d 匹配一個字數字符,/\d/ = /[0-9]/
\D 匹配一個非字數字符,/\D/ = /[^0-9]/
\n 匹配一個換行符
\r 匹配一個回車符
\s 匹配一個空白字符,包括\n,\r,\f,\t,\v等
\S 匹配一個非空白字符,等於/[^\n\f\r\t\v]/
\t 匹配一個製表符
\v 匹配一個重直製表符
\w 匹配一個能夠組成單詞的字符(alphanumeric,這是個人意譯,含數字),包括下劃線,如[\w]匹配"$5.98"中的5,等於[a-zA-Z0-9]
\W 匹配一個不能夠組成單詞的字符,如[\W]匹配"$5.98"中的$,等於[^a-zA-Z0-9]。
var pattern = /s$/; //正則表達式直接量 var pattern = new RegExp("s$"); //構造函數RegExp()
匹配Email地址的正則表達式:w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*
評註:表單驗證時很實用
匹配網址URL的正則表達式:[a-zA-z]+://[^s]*
/^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\*\+,;=.]+$/
評註:網上流傳的版本功能頗有限,上面這個基本能夠知足需求
匹配賬號是否合法(字母開頭,容許5-16字節,容許字母數字下劃線):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
評註:表單驗證時很實用
匹配國內電話號碼:d{3}-d{8}|d{4}-d{7}
評註:匹配形式如 0511-4405222 或 021-87888822
匹配騰訊QQ號:[1-9][0-9]{4,}
評註:騰訊QQ號從10000開始
匹配中國郵政編碼:[1-9]d{5}(?!d)
評註:中國郵政編碼爲6位數字
匹配身份證:d{15}|d{18}
評註:中國的身份證爲15位或18位
手機號:/^1[3|4|5|8][0-9]\d{4,8}$/
^1表明以1開頭,如今中國的手機號沒有是其它開頭的,之後不必定啊
[3|4|5|8] 緊跟上面的1後面,能夠是3或4或5或8的一個數字,若是之後出現190開始的手機號碼了,就須要以下[3|4|5|8|9]
[0-9]表示0-9中間的任何數字,能夠是0或9
\d{4,8} 這個\d跟[0-9]意思同樣,都是0-9中間的數字。{4,8}表示匹配前面的最低4位數字最高8位數字。這裏爲何不是直接的8呢,由於手機號碼歸屬地查詢的時候,根據前7位就能夠知道具體的地址了,後面的4位沒有影響的。
Array、Boolean、Date、Number等對象都具備toString()、toLocaleString()、valueOf()三個方法
var array = new Array("niu","li","na"); console.log(array.valueOf());//Array【3】 console.log(array.toString());//niu,li,na console.log(array.toLocaleString());//niu,li,na
valueOf:返回數組自己
toString():把數組轉換爲字符串,並返回結果,每一項以逗號分割。
toLocalString():把數組轉換爲本地數組,並返回結果。
var boolean = new Boolean(); console.log(boolean.valueOf());//false console.log(boolean.toString());//false
valueOf:返回 Boolean 對象的原始值。
toString():根據原始布爾值或者 booleanObject 對象的值返回字符串 "true" 或 "false"。默認爲"false"。
toLocalString():Boolean對象沒有toLocalString()方法。可是在Boolean對象上使用這個方法也不會報錯。
var date = new Date(); console.log(date.valueOf()); console.log(date.toString()); console.log(date.toLocaleString());
valueOf:返回 Date 對象的原始值,以毫秒錶示。
toString():把 Date 對象轉換爲字符串,並返回結果。使用本地時間表示。
toLocalString():可根據本地時間把 Date 對象轉換爲字符串,並返回結果,返回的字符串根據本地規則格式化。
console.log(Math.PI.valueOf());//3.141592653589793
var num = new Number(1337); console.log(num.valueOf());//1337 console.log(num.toString());//1337 console.log(num.toLocaleString());//1,337
valueOf:返回一個 Number 對象的基本數字值。
toString():把數字轉換爲字符串,使用指定的基數。
toLocalString():把數字轉換爲字符串,使用本地數字格式順序。
var string = new String("abc"); console.log(string.valueOf());//abc console.log(string.toString());//abc
valueOf:返回某個字符串對象的原始值。
toString():返回字符串。
toLocalString()是調用每一個數組元素的 toLocaleString() 方法,而後使用地區特定的分隔符把生成的字符串鏈接起來,造成一個字符串。
toString()方法獲取的是String(傳統字符串),而toLocaleString()方法獲取的是LocaleString(本地環境字符串)。
若是你開發的腳本在世界範圍都有人使用,那麼將對象轉換成字符串時請使用toString()方法來完成。
LocaleString()會根據你機器的本地環境來返回字符串,它和toString()返回的值在不一樣的本地環境下使用的符號會有微妙的變化。
因此使用toString()是保險的,返回惟一值的方法,它不會由於本地環境的改變而發生變化。若是是爲了返回時間類型的數據,推薦使用LocaleString()。如果在後臺處理字符串,請務必使用toString()。
本部分摘自:http://www.javashuo.com/article/p-fthvgxvs-cs.html
未完待續·······