摘要:檢查當前上下文中的參數,建立該對象下的屬性與屬性值。檢查當前上下文的函數聲明,也就是使用關鍵字聲明的函數。數據類型跟布爾值比較回顧下前面說的要點然后有幾個應該要知道的隱形轉換和不能轉換成其他任何值。
前言
2018/04/27 新增六,講解淺拷貝和深拷貝的區別并簡單實現, 七,原生JS操作DOM?
2018/04/30 新增八,解決計算精度問題,例如0.1+0.2?
2018/05/06 修改代碼格式
2018/11/06 新增一個遇到的閉包執行環境的面試題
作者:小小滄海
出處:http://www.cnblogs.com/xxcanghai/
本文地址:http://www.cnblogs.com/xxcanghai/
(題型是一樣的,衹是用我自己的理解去分析,為了不混淆,我略微修改下代碼而已。說來慚愧,雖然我只錯了一題,最難的后面三題都對了,但是思路是錯的,所以希望大家不要衹看答案,重點是學習其中的原理)
function Person() { getAge = function() { console.log(10); }; return this; } Person.getAge = function() { console.log(20); }; Person.prototype.getAge = function() { console.log(30); }; var getAge = function() { console.log(40); }; function getAge() { console.log(50); } Person.getAge(); getAge(); Person().getAge(); getAge(); new Person.getAge(); new Person().getAge(); new new Person().getAge();
(原諒我沒有效果代碼給你們看,因為不知道什么原因無法在編輯器里輸出,你們就放到本地打印看看好了┑( ̄Д  ̄)┍)
這是一道涉及知識點超多的題目,包括函數聲明,變量提升,this指向,new新對象,優先級,原型鏈,繼承,對象屬性和原型屬性等等.
(答案就不貼出來了,你們可以自己跑一下,怕你們忍不住先看正確答案.)
首先分析下上面都做了些什么。
定義一個Person函數,里面有一個getAge的匿名函數
為Person函數本身定義一個靜態屬性getAge函數
為Person函數原型上定義一個getAge函數
變量聲明一個getAge函數表達式
直接聲明一個getAge函數
Person.getAge();拆分開來看
function Person() { getAge = function() { console.log(10); }; return this; } console.log(Person); //看看Person是什么 Person.getAge = function() { console.log(20); }; Person.getAge();
很明顯是直接調用Person的靜態屬性getAge,結果就是20了。(詳情可以參考我之前寫的文章關于Javascript中的new運算符,繼承與原型鏈一些理解)
getAge();首先前面不帶對象,所以可以知道是全局環境調用不用考慮Person的部分
這題考察的是函數聲明和函數表達式
getAge(); //50 var getAge = function() { console.log(40); }; getAge(); //40 function getAge() { console.log(50); } getAge(); //40
上面可以看到首先getAge指向函數聲明,直到函數表達式那一步之后才被覆蓋。
這就要理解Javascript Function兩種類型的區別:用函數聲明創建的函數可以在函數解析后調用(解析時進行等邏輯處理);而用函數表達式創建的函數是在運行時進行賦值,且要等到表達式賦值完成后才能調用。
變量對象的創建,依次經歷了以下幾個過程。
建立arguments對象。檢查當前上下文中的參數,建立該對象下的屬性與屬性值。
檢查當前上下文的函數聲明,也就是使用function關鍵字聲明的函數。在變量對象中以函數名建立一個屬性,屬性值為指向該函數所在內存地址的引用。如果函數名的屬性已經存在,那么該屬性將會被新的引用所覆蓋。
檢查當前上下文中的變量聲明,每找到一個變量聲明,就在變量對象中以變量名建立一個屬性,屬性值為undefined。如果該變量名的屬性已經存在,為了防止同名的函數被修改為undefined,則會直接跳過,原屬性值不會被修改。
所以這一步實際運行順序如下
function getAge() { //函數提升解析賦值給getAge console.log(50); } var getAge; //變量提升,此時getAge為undefined getAge(); //50,此時還是函數聲明 //表達式覆蓋變量賦值 getAge = function() { console.log(40); }; getAge(); //40 getAge(); //40Person().getAge();
前面直接執行Person(),返回一個對象指向全局window,所以不用考慮第二步函數本身定義靜態屬性,也就是window.getAge(),根據上一答知道函數聲明會被覆蓋,也不會進入到原型鏈搜索getAge函數,所以能排除第三步和第五步的干擾.
window.getAge(),這里的關鍵也是一個陷阱在于函數內部的getAge函數賦值是沒帶聲明!!!
區別就在: 如果帶有函數聲明,當執行Person(),因為這是屬于Person函數的局部變量getAge函數,外部調用的全局變量的getAge函數,所以輸出的自然是40
function Person() { var getAge = function() { console.log(10); }; return this; } var getAge = function() { console.log(40); }; Person().getAge();
如果不帶有函數聲明,當執行Person()因為這時屬于Person函數的getAge函數賦值覆蓋外部全局變量的getAge函數,所以輸出的自然是10
function Person() { getAge = function() { console.log(10); }; return this; } var getAge = function() { console.log(40); }; Person().getAge();getAge();
這一步主要受上面影響,也是進一步論證上一步的說法,因為全局變量的getAge函數已經被覆蓋了,所以現在直接調用全局getAge輸出的就是10.
new Person.getAge();這里的難點在于符號優先級
(截圖來自運算符優先級)
一般人認為是這樣子的
(new Person).getAge(); // 10
從截圖可以知道
成員訪問(點符號)= new(帶參數列表) > 函數調用 = new (無參數列表)
其實當時這一步看了優先級之后從那角度去理解我就迷糊,如果優先級為準,怎么運行都會報錯的,先運行點符號,然后new沒帶參數應該函數調用優先,最后才到new那一步
new ((Person.getAge)()); //Uncaught TypeError: Person.getAge(...) is not a constructor
后來看到評論說了才明白一些,我理解錯了一些地方。
首先帶參數的new不是說必須傳參才算,而是后面帶了括號就算是帶參了。
new Person()//帶參 new Person//沒帶參
實際上是這樣子的,從整體去了解
new (Person.getAge)();//20
因為不帶參數的new優先級不如成員訪問(點符號),所以首先執行Person.getAge.
然后new(帶參數列表)優先級高于函數調用,所以將Person.getAge函數作為了構造函數來執行如new xxx()實例化一個東西出來
最好先弄清這一題的來龍去脈,然后才能解決后面更加繞的問題。
理解了運算符優先級問題之后下面其實都好做
先來一步步剖析,
題目里成員訪問(點符號)和new(帶參數列表)最優先,同優先級情況下從左往右計算,所以先執行new Person();
因為Person函數內的getAge 只是一個賦值函數,所以所有實例都沒有繼承這個函數,只能從原型鏈上尋找到getAge函數,(輸出30那個)
(new Person()).getAge();//30new new Person().getAge();
承接上面步驟繼續一步步剖析,
題目里成員訪問(點符號)和new(帶參數列表)最優先,同優先級情況下從左往右計算,所以先執行new Person();
因為Person函數內的getAge 只是一個賦值函數,所以所有實例都沒有繼承這個函數,只能從原型鏈上尋找到getAge函數,(輸出30那個),所以先執行new Person().getAge;
將new Person().getAge函數作為了構造函數來執行如new xxx()實例化一個東西出來,執行new ((new Person()).getAge)(),結果還是輸出30
大概意思就這樣了,不知道我講清楚了沒有?
二,關于強制轉換類型(這是我在研究隱形轉換的時候折騰出來的問題,里面彎彎繞繞挺多的,可足以坑死很多人,看看你們能不能做全對)
console.log(Number(null)); console.log(Number(undefined)); console.log(Number({})); console.log(Number({abc: 123})); console.log(undefined == null); console.log(NaN == null); console.log(null == null); console.log(NaN == NaN); console.log([1] == true); console.log([[1], [2], [3]] == "1,2,3"); console.log("" == false); console.log(null == false); console.log({} == false); console.log({} == []); console.log([] == false); console.log([] == []); console.log(![] == false); console.log(![] == []); console.log(new Boolean(true) == 1); console.log(new Boolean(false) == 0); console.log(new Boolean(true) ? true : false); console.log(new Boolean(false) ? true : false);
這是一道涉及知識點不算多,但是很能體現javascript語言的奇形怪狀,其實所有的規律我都已經寫出來過了,這些題型也是從里面想出來的,(詳情可以參考我之前寫的文章javascript中關于相等符號的隱形轉換)
先看看關于Number的問題,
如果是null值,返回0。
如果是undefined,返回NaN。
{}先調用對象的valueOf()方法還是{},再調用toString()方法輸出"[object Object]",得出字符串再調用Number()因為無效字符串返回NaN
同上
console.log(Number(null)); //0 console.log(Number(undefined)); //NaN console.log(Number({})); //NaN console.log(Number({abc: 123})); //NaN
接著是關于undefined,NaN ,null之間的不完全相等關系,
null和undefined是相等的,undefined和undefined是相等的,null和null也是相等的,
但是如果有一個操作數是NaN則相等操作符返回false,而不相等操作符返回true。(即使兩個操作數都是NaN,相等操作符也返回false因為按照規則NaN不等于NaN。)
console.log(undefined == null); //true console.log(NaN == null); //false console.log(null == null); //true console.log(NaN == NaN); //false
下面關于轉換類型問題
1, 如果一個操作數是布爾值.則在比較相等性之前先將其轉換為數值false轉換為0,而true轉換為1;
2, 如果一個操作數是字符串,另一個操作數是數值,在比較相等性之前先將字符串調用Number() 轉換為數值;
3, 如果一個操作數是對象,另一個操作數不是,則先調用對象的 valueOf, 再調用對象的 toString 與基本類型進行比較。也就是說先轉成 number 型,不滿足就繼續再轉成 string 類型按照前面的規則進行比較;
[1]先調用對象的valueOf()方法還是[1],再調用toString()方法輸出"1",得出字符串再調用Number()返回1,
true調用Number()直接返回1
[[1],[2],[3]]先調用對象的valueOf()方法還是[[1],[2],[3]],再調用toString()方法輸出"1,2,3"直接比較
(關于toString(),如果是Array值,將 Array 的每個元素轉換為字符串,并用逗號作為分隔符進行拼接。)
console.log([1] == true); //true console.log([[1], [2], [3]] == "1,2,3"); //true
數據類型跟布爾值比較,回顧下前面說的要點,然后有幾個應該要知道的隱形轉換:
null和undefined不能轉換成其他任何值。
false -> 0.
[] -> 0.
{} -> NaN.
然后可以做出大部分題型了
0==0
null==0
NaN==0
迷惑題,如果兩個操作數都是對象,則比較它們是不是同一個對象。如果兩個操作數都指向同一個對象,則相等操作符返回true;否則返回false(容易固定思維轉化比較,盡管結果也對,但是不會轉換類型比較的)
0==0
迷惑題,雖然都長得一樣,但是引用類型指向地址不同就不會相等(詳情可以參考我之前寫的文章關于javascript基本類型和引用類型小知識)
迷惑題,加了取反符號之后就要考慮情況更多了,
首先根據上一題答案里有講解過符號優先級的問題,!優先級高于==,所以前面是一個判斷,[] == true,取反就是false了,
(![] ? true : false) == [] -> 0 == 0
這題主要難點在于考慮符號優先級的問題,先判斷再比較.
同7
console.log("" == false); //true console.log(null == false); //false console.log({} == false); //false console.log({} == []); //false console.log([] == false); //true console.log([] == []); //false console.log(![] == false); //true console.log(![] == []); //true
最后跟構造函數有關,容易被誤導(詳情可以參考我之前寫的文章關于Javascript中的構造函數,原型鏈與new運算符一些理解)
構造函數創建一個用戶定義的對象類型的實例或具有構造函數的內置對象類型之一,所以new Boolean返回的不是布爾值,而是內置對象類型,詳情如下
console.log(typeof Boolean(true)); console.log(typeof new Boolean(true)); console.log(typeof new Boolean(true).valueOf()); console.log(typeof new Boolean(true).toString());
所以知道結果如下:
new Boolean(true)先調用對象的valueOf()方法返回true,再調用toString()方法輸出"true",得出字符串再調用Number()返回1
原理如上
3和4,這里不是比較,而是if判斷,因為存在對象,所以都是返回true
console.log(new Boolean(true) == 1); //true console.log(new Boolean(false) == 0); //true console.log(new Boolean(true) ? true : false); //true console.log(new Boolean(false) ? true : false); //true三,關于z-index 層級樹
依據結構樣式,給出例子里的層級先后順序,這里不太好說,大家直接看看原理再試一次看看吧
css中z-index層級
CSS z-index 屬性的使用方法和層級樹的概念
CSS基礎(七):z-index詳解
深入理解CSS定位中的堆疊z-index
我給一個顏色版給大家做參考
四,關于深入理解JS中的Function.prototype.bind()方法原理&兼容寫法
一般來說我們想到改變函數this指向的方法無非就call、apply和bind;
方法名 | 描述 |
---|---|
call | 調用一個對象的一個方法,以另一個對象替換當前對象,余參按順序傳遞 |
apply | 調用一個對象的一個方法,以另一個對象替換當前對象,余參按數組傳遞 |
bind | 創建一個新函數,稱為綁定函數,當調用這個綁定函數時,綁定函數會以創建它時傳入 bind()方法的第一個參數作為 this,傳入 bind() 方法的第二個以及以后的參數加上綁定函數運行時本身的參數按照順序作為原函數的參數來調用原函數 |
他們之間的區別在于調用時機或者添加參數形式不同,所以我們可以用里面的方法做兼容.
Prototype.js中的寫法
Function.prototype.bind = function() { var fn = this, args = [].prototype.slice.call(arguments), object = args.shift(); return function() { return fn.apply(object, args.concat([].prototype.slice.call(arguments))); }; };
Firefox為bind提供了一個兼容實現
if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== "function") { throw new TypeError( "Function.prototype.bind - what is trying to be bound is not callable" ); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function() {}, fBound = function() { return fToBind.apply( this instanceof fNOP && oThis ? this : oThis || window, aArgs.concat(Array.prototype.slice.call(arguments)) ); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; }五,前端經典面試題: 從輸入URL到頁面加載發生了什么?
親身遇到面試題之一,知識點太多了,贈你飛機票前端經典面試題: 從輸入URL到頁面加載發生了什么?
六,講解淺拷貝和深拷貝的區別并簡單實現簡單直觀點說:
淺拷貝只會將原對象的各個屬性進行依次復制,而 JavaScript 存儲對象都是存地址的,所以淺復制會導致深層對象屬性都指向同一塊內存地址;
深拷貝不僅將原對象的各個屬性進行依次復制,而且將原對象的深層對象屬性也依次采用深拷貝的方法遞歸復制到新對象上;
(詳情可以參考我之前寫的文章關于javascript基本類型和引用類型小知識)
淺拷貝簡單實現:function shallowCopy(obj) { var _obj = {}, key; //如果使用Object.keys更方便 for (key in obj) { //只復制對象本身首層屬性 if (obj.hasOwnProperty(key)) { _obj[key] = obj[key]; } } return _obj; } var obj1 = { a: 1, b: { c: 2, }, }, obj2; //復制對象并改變原對象值 obj2 = shallowCopy(obj1); obj1.a = 2; obj1.b.c = 3; console.log(obj1, obj2);淺拷貝ES5實現:
Object.assign() 方法用于將所有可枚舉屬性的值從一個或多個源對象復制到目標對象。它將返回目標對象
function shallowCopy(obj) { return Object.assign({}, obj); } var obj1 = { a: 1, b: { c: 2, }, }, obj2; //復制對象并改變原對象值 obj2 = shallowCopy(obj1); obj1.a = 2; obj1.b.c = 3; console.log(obj1, obj2);深拷貝簡單實現:
function deepCopy(obj) { var _obj = {}, key; //如果使用Object.keys更方便 for (key in obj) { if (obj.hasOwnProperty(key)) { if (typeof obj[key] === "object") { _obj[key] = deepCopy(obj[key]); } else { _obj[key] = obj[key]; } } } return _obj; } var obj1 = { a: 1, b: { c: 2, }, }, obj2; //復制對象并改變原對象值 obj2 = deepCopy(obj1); obj1.a = 2; obj1.b.c = 3; console.log(obj1, obj2);
注意:
for in遍歷性能太差,會包括對象原型鏈上的屬性,即使加上hasOwnProperty判斷也只是減少操作屬性;
有人會在遞歸使用arguments.callee方法,但是ECMAscript5 的 Strict Mode是禁止使用的,因為arguments是龐大且變化的Why was the arguments.callee.caller property deprecated in JavaScript?
函數名調用好處:
這個函數可以像其他任何代碼一樣在代碼中調用。
它不會污染名稱空間。
它的值不會改變。
它性能更好(訪問參數對象是昂貴的)。
深拷貝轉格式寫法:序列化成JSON字符串的值會新開一個存儲地址,從而分開兩者關聯;
function deepCopy(obj) { return JSON.parse(JSON.stringify(obj)); } var obj1 = { a: 1, b: { c: 2, }, }, obj2; //復制對象并改變原對象值 obj2 = deepCopy(obj1); obj1.a = 2; obj1.b.c = 3; console.log(obj1, obj2);
注意:
只能處理能夠被 json 直接表示的數據結構(Number, String, Boolean, Array, 扁平對象等),不支持NaN,Infinity,循環引用和function等;
如果構造實例,會切斷原有對象的constructor等相關屬性;
深拷貝Object.create寫法:創建一個具有指定原型且可選擇性地包含指定屬性的對象
function deepCopy(obj) { var _obj = {}, key; //如果使用Object.keys更方便 for (key in obj) { if (obj.hasOwnProperty(key)) { if (typeof obj[key] === "object") { _obj[key] = Object.create(obj[key]); } else { _obj[key] = obj[key]; } } } return _obj; } var obj1 = { a: 1, b: { c: 2, }, }, obj2; //復制對象并改變原對象值 obj2 = deepCopy(obj1); obj1.a = 2; obj1.b.c = 3; console.log(obj1, obj2);
注意:
屬性不在復制對象本身屬性中,而在原型鏈上;
(詳情可以參考我之前寫的文章關于創建對象的三種寫法 ---- 字面量,new構造器和Object.create())
function deepCopy(obj) { var _obj = obj.constructor === Array ? [] : {}; if (window.JSON) { _obj = JSON.parse(JSON.stringify(obj)); } else { Object.keys(obj).map(key => { _obj[key] = typeof obj[key] === "object" ? deepCopy(obj[key]) : obj[key]; }); } return _obj; } var obj1 = { a: 1, b: { c: 2, }, }, obj2; //復制對象并改變原對象值 obj2 = deepCopy(obj1); obj1.a = 2; obj1.b.c = 3; console.log(obj1, obj2);七,原生JS操作DOM?
說多都是淚,從原生到JQ到MV*框架,老祖宗都模糊了.
查找節點方法 | 作用 |
---|---|
document.getElementById | 根據ID查找元素,大小寫敏感,如果有多個結果,只返回第一個 |
document.getElementsByClassName | 根據類名查找元素,多個類名用空格分隔,返回一個 HTMLCollection 。注意兼容性為IE9+(含)。另外,不僅僅是document,其它元素也支持 getElementsByClassName 方法 |
document.getElementsByTagName | 根據標簽查找元素, * 表示查詢所有標簽,返回一個 HTMLCollection |
document.getElementsByName | 根據元素的name屬性查找,返回一個 NodeList |
document.querySelector | 指定一個或多個匹配元素的 CSS 選擇器。 可以使用它們的 id, 類, 類型, 屬性, 屬性值等來選取元素。返回單個Node,IE8+(含),如果匹配到多個結果,只返回第一個 |
document.querySelectorAll | 指定一個或多個匹配元素的 CSS 選擇器。 可以使用它們的 id, 類, 類型, 屬性, 屬性值等來選取元素。返回一個 NodeList ,IE8+(含) |
document.forms | 獲取當前頁面所有form,返回一個 HTMLCollection |
方法 | 作用 |
---|---|
document.createElement | 創建元素 |
document.createTextNode | 創建文本 |
document.cloneNode | 克隆元素,接收一個bool參數,用來表示是否復制子元素。 |
document.createDocumentFragment | 創建文檔碎片 |
document.createComment | 創建注釋節點 |
//創建并插入文本 var ele = document.createElement("div"), txt = document.createTextNode("123"), cmt = document.createComment("comments"); ele.appendChild(txt); ele.appendChild(cmt); //克隆元素 var clone_ele1 = ele.cloneNode(), clone_ele2 = ele.cloneNode(true); console.log(ele); console.log(txt); console.log(clone_ele1); console.log(clone_ele2);修改節點
方法 | 作用 |
---|---|
parent.appendChild(child) | 將child追加到parent的子節點的最后面 |
parentNode.insertBefore(newNode, refNode) | 將某個節點插入到另外一個節點的前面 |
parent.removeChild(child) | 刪除指定的子節點并返回子節點 |
parent.replaceChild(child) | 將一個節點替換另一個節點 |
parent.insertData(child) | 將數據插入已有的文本節點中 |
//創建并插入文本 var ele = document.createElement("div"), txt = document.createTextNode("123"), txt2 = document.createTextNode("456"), cmt = document.createComment("comments"); ele.appendChild(txt); ele.insertBefore(cmt, txt); ele.removeChild(cmt); ele.replaceChild(txt2, txt); txt.insertData(0, "789"); console.log(ele);八,解決計算精度問題,例如0.1+0.2? toFixed()問題:
返回的是字符串;
會強制保留限定小數位;
某些瀏覽器對于小數的進位有點不同;
console.log((0.1 + 0.2).toFixed(2));網上流傳的方法,思路就是把數字轉換整數然后再除回原位數:
/** * floatTool 包含加減乘除四個方法,能確保浮點數運算不丟失精度 * * 我們知道計算機編程語言里浮點數計算會存在精度丟失問題(或稱舍入誤差),其根本原因是二進制和實現位數限制有些數無法有限表示 * 以下是十進制小數對應的二進制表示 * 0.1 >> 0.0001 1001 1001 1001…(1001無限循環) * 0.2 >> 0.0011 0011 0011 0011…(0011無限循環) * 計算機里每種數據類型的存儲是一個有限寬度,比如 JavaScript 使用 64 位存儲數字類型,因此超出的會舍去。舍去的部分就是精度丟失的部分。 * * ** method ** * add / subtract / multiply /divide * * ** explame ** * 0.1 + 0.2 == 0.30000000000000004 (多了 0.00000000000004) * 0.2 + 0.4 == 0.6000000000000001 (多了 0.0000000000001) * 19.9 * 100 == 1989.9999999999998 (少了 0.0000000000002) * * floatObj.add(0.1, 0.2) >> 0.3 * floatObj.multiply(19.9, 100) >> 1990 * */ var floatTool = (function() { /* * 判斷obj是否為一個整數 */ function isInteger(obj) { return Math.floor(obj) === obj; } /* * 將一個浮點數轉成整數,返回整數和倍數。如 3.14 >> 314,倍數是 100 * @param floatNum {number} 小數 * @return {object} * {times:100, num: 314} */ function toInteger(floatNum) { var ret = { times: 1, num: 0, }; if (isInteger(floatNum)) { ret.num = floatNum; return ret; } var strfi = floatNum + ""; var dotPos = strfi.indexOf("."); var len = strfi.substr(dotPos + 1).length; var times = Math.pow(10, len); var intNum = parseInt(floatNum * times + 0.5, 10); ret.times = times; ret.num = intNum; return ret; } /* * 核心方法,實現加減乘除運算,確保不丟失精度 * 思路:把小數放大為整數(乘),進行算術運算,再縮小為小數(除) * * @param a {number} 運算數1 * @param b {number} 運算數2 * @param digits {number} 精度,保留的小數點數,比如 2, 即保留為兩位小數 * @param op {string} 運算類型,有加減乘除(add/subtract/multiply/divide) * */ function operation(a, b, op) { var o1 = toInteger(a); var o2 = toInteger(b); var n1 = o1.num; var n2 = o2.num; var t1 = o1.times; var t2 = o2.times; var max = t1 > t2 ? t1 : t2; var result = null; switch (op) { case "add": if (t1 === t2) { // 兩個小數位數相同 result = n1 + n2; } else if (t1 > t2) { // o1 小數位 大于 o2 result = n1 + n2 * (t1 / t2); } else { // o1 小數位 小于 o2 result = n1 * (t2 / t1) + n2; } return result / max; case "subtract": if (t1 === t2) { result = n1 - n2; } else if (t1 > t2) { result = n1 - n2 * (t1 / t2); } else { result = n1 * (t2 / t1) - n2; } return result / max; case "multiply": result = (n1 * n2) / (t1 * t2); return result; case "divide": return (result = (function() { var r1 = n1 / n2; var r2 = t2 / t1; return operation(r1, r2, "multiply"); })()); } } // 加減乘除的四個接口 function add(a, b) { return operation(a, b, "add"); } function subtract(a, b) { return operation(a, b, "subtract"); } function multiply(a, b) { return operation(a, b, "multiply"); } function divide(a, b) { return operation(a, b, "divide"); } // exports return { add: add, subtract: subtract, multiply: multiply, divide: divide, }; })();九,遞歸閉包函數
function fun(n, o) { console.log(o); return { fun: function(m) { return fun(m, n); }, }; }情況一
var a = fun(0) // undefined a.fun(1) // 0 a.fun(2) // 0 a.fun(3) // 0
因為不斷遞歸看起來會很迷糊,我們試著把它拆分出現展示
n = 0; o = undefined; a = { fun: function(m) { return fun(m, n); } }; -------------------- m = 1; n = 0; a = { fun: function(m) { return fun(m, n); } }; -------------------- m = 2; n = 0; a = { fun: function(m) { return fun(m, n); } }; -------------------- m = 3; n = 0; a = { fun: function(m) { return fun(m, n); } };
干擾代碼很多,但是實際上只有第一次執行的時候會賦值給o,后續調用都只是改變n值
情況二var b = fun(0).fun(1).fun(2).fun(3); // undefined // 0 // 1 // 2
這個乍看之下和上面沒什么區別,但是結果挺詫異的,我也想了好久,還有一個糾結地方是我當時不記得執行方法和.運算符誰的優先級比較高
n = 0; o = undefined; a = { fun: function(m) { return fun(m, n); } }; -------------------- m = 1; n = 0; a = { fun: function(m) { return fun(m, n); } }; -------------------- m = 2; n = 1; a = { fun: function(m) { return fun(m, n); } }; -------------------- m = 3; n = 2; a = { fun: function(m) { return fun(m, n); } };
最后想起情況一其實是屬于閉包的用法,還涉及到執行環境和作用域的知識,因為它是屬于執行完后只保存第一次變量o,所以除了第一次能夠正常賦值之后后續都是只賦值m,所以輸出結果都是0
(詳情可以參考我之前寫的文章Javascript難點知識運用---遞歸,閉包,柯里化等(不定時更新))
情況二就比較特殊,因為他整個執行環境運行過程中都能夠訪問改變o,具體還是得靠自己理解一下,我不知道怎么表達出來
var c = fun(0).fun(1) c.fun(2) c.fun(3) // undefined // 0 // 1 // 1
如果理解上面兩種情況的話,這題問題就簡單了.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/106410.html
摘要:責編現代化的方式開發一個圖片上傳工具前端掘金對于圖片上傳,大家一定不陌生。之深入事件機制前端掘金事件綁定的方式原生的事件綁定方式有幾種想必有很多朋友說種目前,在本人目前的研究中,只有兩種半兩種半還有半種的且聽我道來。 Ajax 與數據傳輸 - 前端 - 掘金背景 在沒有ajax之前,前端與后臺傳數據都是靠表單傳輸,使用表單的方法傳輸數據有一個比較大的問題就是每次提交數據都會刷新頁面,用...
閱讀 1338·2023-04-25 15:21
閱讀 2670·2021-11-24 10:23
閱讀 3397·2021-10-11 10:59
閱讀 3242·2021-09-03 10:28
閱讀 1731·2019-08-26 13:45
閱讀 2319·2019-08-26 12:11
閱讀 921·2019-08-26 12:00
閱讀 1705·2019-08-26 10:44