摘要:檢查當(dāng)前上下文中的參數(shù),建立該對(duì)象下的屬性與屬性值。檢查當(dāng)前上下文的函數(shù)聲明,也就是使用關(guān)鍵字聲明的函數(shù)。數(shù)據(jù)類型跟布爾值比較回顧下前面說(shuō)的要點(diǎn)然后有幾個(gè)應(yīng)該要知道的隱形轉(zhuǎn)換和不能轉(zhuǎn)換成其他任何值。
前言
2018/04/27 新增六,講解淺拷貝和深拷貝的區(qū)別并簡(jiǎn)單實(shí)現(xiàn), 七,原生JS操作DOM?
2018/04/30 新增八,解決計(jì)算精度問(wèn)題,例如0.1+0.2?
2018/05/06 修改代碼格式
2018/11/06 新增一個(gè)遇到的閉包執(zhí)行環(huán)境的面試題
作者:小小滄海
出處:http://www.cnblogs.com/xxcanghai/
本文地址:http://www.cnblogs.com/xxcanghai/
(題型是一樣的,衹是用我自己的理解去分析,為了不混淆,我略微修改下代碼而已。說(shuō)來(lái)慚愧,雖然我只錯(cuò)了一題,最難的后面三題都對(duì)了,但是思路是錯(cuò)的,所以希望大家不要衹看答案,重點(diǎn)是學(xué)習(xí)其中的原理)
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();
(原諒我沒(méi)有效果代碼給你們看,因?yàn)椴恢朗裁丛驘o(wú)法在編輯器里輸出,你們就放到本地打印看看好了┑( ̄Д  ̄)┍)
這是一道涉及知識(shí)點(diǎn)超多的題目,包括函數(shù)聲明,變量提升,this指向,new新對(duì)象,優(yōu)先級(jí),原型鏈,繼承,對(duì)象屬性和原型屬性等等.
(答案就不貼出來(lái)了,你們可以自己跑一下,怕你們?nèi)滩蛔∠瓤凑_答案.)
首先分析下上面都做了些什么。
定義一個(gè)Person函數(shù),里面有一個(gè)getAge的匿名函數(shù)
為Person函數(shù)本身定義一個(gè)靜態(tài)屬性getAge函數(shù)
為Person函數(shù)原型上定義一個(gè)getAge函數(shù)
變量聲明一個(gè)getAge函數(shù)表達(dá)式
直接聲明一個(gè)getAge函數(shù)
Person.getAge();拆分開(kāi)來(lái)看
function Person() { getAge = function() { console.log(10); }; return this; } console.log(Person); //看看Person是什么 Person.getAge = function() { console.log(20); }; Person.getAge();
很明顯是直接調(diào)用Person的靜態(tài)屬性getAge,結(jié)果就是20了。(詳情可以參考我之前寫的文章關(guān)于Javascript中的new運(yùn)算符,繼承與原型鏈一些理解)
getAge();首先前面不帶對(duì)象,所以可以知道是全局環(huán)境調(diào)用不用考慮Person的部分
這題考察的是函數(shù)聲明和函數(shù)表達(dá)式
getAge(); //50 var getAge = function() { console.log(40); }; getAge(); //40 function getAge() { console.log(50); } getAge(); //40
上面可以看到首先getAge指向函數(shù)聲明,直到函數(shù)表達(dá)式那一步之后才被覆蓋。
這就要理解Javascript Function兩種類型的區(qū)別:用函數(shù)聲明創(chuàng)建的函數(shù)可以在函數(shù)解析后調(diào)用(解析時(shí)進(jìn)行等邏輯處理);而用函數(shù)表達(dá)式創(chuàng)建的函數(shù)是在運(yùn)行時(shí)進(jìn)行賦值,且要等到表達(dá)式賦值完成后才能調(diào)用。
變量對(duì)象的創(chuàng)建,依次經(jīng)歷了以下幾個(gè)過(guò)程。
建立arguments對(duì)象。檢查當(dāng)前上下文中的參數(shù),建立該對(duì)象下的屬性與屬性值。
檢查當(dāng)前上下文的函數(shù)聲明,也就是使用function關(guān)鍵字聲明的函數(shù)。在變量對(duì)象中以函數(shù)名建立一個(gè)屬性,屬性值為指向該函數(shù)所在內(nèi)存地址的引用。如果函數(shù)名的屬性已經(jīng)存在,那么該屬性將會(huì)被新的引用所覆蓋。
檢查當(dāng)前上下文中的變量聲明,每找到一個(gè)變量聲明,就在變量對(duì)象中以變量名建立一個(gè)屬性,屬性值為undefined。如果該變量名的屬性已經(jīng)存在,為了防止同名的函數(shù)被修改為undefined,則會(huì)直接跳過(guò),原屬性值不會(huì)被修改。
所以這一步實(shí)際運(yùn)行順序如下
function getAge() { //函數(shù)提升解析賦值給getAge console.log(50); } var getAge; //變量提升,此時(shí)getAge為undefined getAge(); //50,此時(shí)還是函數(shù)聲明 //表達(dá)式覆蓋變量賦值 getAge = function() { console.log(40); }; getAge(); //40 getAge(); //40Person().getAge();
前面直接執(zhí)行Person(),返回一個(gè)對(duì)象指向全局window,所以不用考慮第二步函數(shù)本身定義靜態(tài)屬性,也就是window.getAge(),根據(jù)上一答知道函數(shù)聲明會(huì)被覆蓋,也不會(huì)進(jìn)入到原型鏈搜索getAge函數(shù),所以能排除第三步和第五步的干擾.
window.getAge(),這里的關(guān)鍵也是一個(gè)陷阱在于函數(shù)內(nèi)部的getAge函數(shù)賦值是沒(méi)帶聲明!!!
區(qū)別就在: 如果帶有函數(shù)聲明,當(dāng)執(zhí)行Person(),因?yàn)檫@是屬于Person函數(shù)的局部變量getAge函數(shù),外部調(diào)用的全局變量的getAge函數(shù),所以輸出的自然是40
function Person() { var getAge = function() { console.log(10); }; return this; } var getAge = function() { console.log(40); }; Person().getAge();
如果不帶有函數(shù)聲明,當(dāng)執(zhí)行Person()因?yàn)檫@時(shí)屬于Person函數(shù)的getAge函數(shù)賦值覆蓋外部全局變量的getAge函數(shù),所以輸出的自然是10
function Person() { getAge = function() { console.log(10); }; return this; } var getAge = function() { console.log(40); }; Person().getAge();getAge();
這一步主要受上面影響,也是進(jìn)一步論證上一步的說(shuō)法,因?yàn)槿肿兞康膅etAge函數(shù)已經(jīng)被覆蓋了,所以現(xiàn)在直接調(diào)用全局getAge輸出的就是10.
new Person.getAge();這里的難點(diǎn)在于符號(hào)優(yōu)先級(jí)
(截圖來(lái)自運(yùn)算符優(yōu)先級(jí))
一般人認(rèn)為是這樣子的
(new Person).getAge(); // 10
從截圖可以知道
成員訪問(wèn)(點(diǎn)符號(hào))= new(帶參數(shù)列表) > 函數(shù)調(diào)用 = new (無(wú)參數(shù)列表)
其實(shí)當(dāng)時(shí)這一步看了優(yōu)先級(jí)之后從那角度去理解我就迷糊,如果優(yōu)先級(jí)為準(zhǔn),怎么運(yùn)行都會(huì)報(bào)錯(cuò)的,先運(yùn)行點(diǎn)符號(hào),然后new沒(méi)帶參數(shù)應(yīng)該函數(shù)調(diào)用優(yōu)先,最后才到new那一步
new ((Person.getAge)()); //Uncaught TypeError: Person.getAge(...) is not a constructor
后來(lái)看到評(píng)論說(shuō)了才明白一些,我理解錯(cuò)了一些地方。
首先帶參數(shù)的new不是說(shuō)必須傳參才算,而是后面帶了括號(hào)就算是帶參了。
new Person()//帶參 new Person//沒(méi)帶參
實(shí)際上是這樣子的,從整體去了解
new (Person.getAge)();//20
因?yàn)椴粠?shù)的new優(yōu)先級(jí)不如成員訪問(wèn)(點(diǎn)符號(hào)),所以首先執(zhí)行Person.getAge.
然后new(帶參數(shù)列表)優(yōu)先級(jí)高于函數(shù)調(diào)用,所以將Person.getAge函數(shù)作為了構(gòu)造函數(shù)來(lái)執(zhí)行如new xxx()實(shí)例化一個(gè)東西出來(lái)
最好先弄清這一題的來(lái)龍去脈,然后才能解決后面更加繞的問(wèn)題。
理解了運(yùn)算符優(yōu)先級(jí)問(wèn)題之后下面其實(shí)都好做
先來(lái)一步步剖析,
題目里成員訪問(wèn)(點(diǎn)符號(hào))和new(帶參數(shù)列表)最優(yōu)先,同優(yōu)先級(jí)情況下從左往右計(jì)算,所以先執(zhí)行new Person();
因?yàn)镻erson函數(shù)內(nèi)的getAge 只是一個(gè)賦值函數(shù),所以所有實(shí)例都沒(méi)有繼承這個(gè)函數(shù),只能從原型鏈上尋找到getAge函數(shù),(輸出30那個(gè))
(new Person()).getAge();//30new new Person().getAge();
承接上面步驟繼續(xù)一步步剖析,
題目里成員訪問(wèn)(點(diǎn)符號(hào))和new(帶參數(shù)列表)最優(yōu)先,同優(yōu)先級(jí)情況下從左往右計(jì)算,所以先執(zhí)行new Person();
因?yàn)镻erson函數(shù)內(nèi)的getAge 只是一個(gè)賦值函數(shù),所以所有實(shí)例都沒(méi)有繼承這個(gè)函數(shù),只能從原型鏈上尋找到getAge函數(shù),(輸出30那個(gè)),所以先執(zhí)行new Person().getAge;
將new Person().getAge函數(shù)作為了構(gòu)造函數(shù)來(lái)執(zhí)行如new xxx()實(shí)例化一個(gè)東西出來(lái),執(zhí)行new ((new Person()).getAge)(),結(jié)果還是輸出30
大概意思就這樣了,不知道我講清楚了沒(méi)有?
二,關(guān)于強(qiáng)制轉(zhuǎn)換類型(這是我在研究隱形轉(zhuǎn)換的時(shí)候折騰出來(lái)的問(wèn)題,里面彎彎繞繞挺多的,可足以坑死很多人,看看你們能不能做全對(duì))
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);
這是一道涉及知識(shí)點(diǎn)不算多,但是很能體現(xiàn)javascript語(yǔ)言的奇形怪狀,其實(shí)所有的規(guī)律我都已經(jīng)寫出來(lái)過(guò)了,這些題型也是從里面想出來(lái)的,(詳情可以參考我之前寫的文章javascript中關(guān)于相等符號(hào)的隱形轉(zhuǎn)換)
先看看關(guān)于Number的問(wèn)題,
如果是null值,返回0。
如果是undefined,返回NaN。
{}先調(diào)用對(duì)象的valueOf()方法還是{},再調(diào)用toString()方法輸出"[object Object]",得出字符串再調(diào)用Number()因?yàn)闊o(wú)效字符串返回NaN
同上
console.log(Number(null)); //0 console.log(Number(undefined)); //NaN console.log(Number({})); //NaN console.log(Number({abc: 123})); //NaN
接著是關(guān)于undefined,NaN ,null之間的不完全相等關(guān)系,
null和undefined是相等的,undefined和undefined是相等的,null和null也是相等的,
但是如果有一個(gè)操作數(shù)是NaN則相等操作符返回false,而不相等操作符返回true。(即使兩個(gè)操作數(shù)都是NaN,相等操作符也返回false因?yàn)榘凑找?guī)則NaN不等于NaN。)
console.log(undefined == null); //true console.log(NaN == null); //false console.log(null == null); //true console.log(NaN == NaN); //false
下面關(guān)于轉(zhuǎn)換類型問(wèn)題
1, 如果一個(gè)操作數(shù)是布爾值.則在比較相等性之前先將其轉(zhuǎn)換為數(shù)值false轉(zhuǎn)換為0,而true轉(zhuǎn)換為1;
2, 如果一個(gè)操作數(shù)是字符串,另一個(gè)操作數(shù)是數(shù)值,在比較相等性之前先將字符串調(diào)用Number() 轉(zhuǎn)換為數(shù)值;
3, 如果一個(gè)操作數(shù)是對(duì)象,另一個(gè)操作數(shù)不是,則先調(diào)用對(duì)象的 valueOf, 再調(diào)用對(duì)象的 toString 與基本類型進(jìn)行比較。也就是說(shuō)先轉(zhuǎn)成 number 型,不滿足就繼續(xù)再轉(zhuǎn)成 string 類型按照前面的規(guī)則進(jìn)行比較;
[1]先調(diào)用對(duì)象的valueOf()方法還是[1],再調(diào)用toString()方法輸出"1",得出字符串再調(diào)用Number()返回1,
true調(diào)用Number()直接返回1
[[1],[2],[3]]先調(diào)用對(duì)象的valueOf()方法還是[[1],[2],[3]],再調(diào)用toString()方法輸出"1,2,3"直接比較
(關(guān)于toString(),如果是Array值,將 Array 的每個(gè)元素轉(zhuǎn)換為字符串,并用逗號(hào)作為分隔符進(jìn)行拼接。)
console.log([1] == true); //true console.log([[1], [2], [3]] == "1,2,3"); //true
數(shù)據(jù)類型跟布爾值比較,回顧下前面說(shuō)的要點(diǎn),然后有幾個(gè)應(yīng)該要知道的隱形轉(zhuǎn)換:
null和undefined不能轉(zhuǎn)換成其他任何值。
false -> 0.
[] -> 0.
{} -> NaN.
然后可以做出大部分題型了
0==0
null==0
NaN==0
迷惑題,如果兩個(gè)操作數(shù)都是對(duì)象,則比較它們是不是同一個(gè)對(duì)象。如果兩個(gè)操作數(shù)都指向同一個(gè)對(duì)象,則相等操作符返回true;否則返回false(容易固定思維轉(zhuǎn)化比較,盡管結(jié)果也對(duì),但是不會(huì)轉(zhuǎn)換類型比較的)
0==0
迷惑題,雖然都長(zhǎng)得一樣,但是引用類型指向地址不同就不會(huì)相等(詳情可以參考我之前寫的文章關(guān)于javascript基本類型和引用類型小知識(shí))
迷惑題,加了取反符號(hào)之后就要考慮情況更多了,
首先根據(jù)上一題答案里有講解過(guò)符號(hào)優(yōu)先級(jí)的問(wèn)題,!優(yōu)先級(jí)高于==,所以前面是一個(gè)判斷,[] == true,取反就是false了,
(![] ? true : false) == [] -> 0 == 0
這題主要難點(diǎn)在于考慮符號(hào)優(yōu)先級(jí)的問(wèn)題,先判斷再比較.
同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
最后跟構(gòu)造函數(shù)有關(guān),容易被誤導(dǎo)(詳情可以參考我之前寫的文章關(guān)于Javascript中的構(gòu)造函數(shù),原型鏈與new運(yùn)算符一些理解)
構(gòu)造函數(shù)創(chuàng)建一個(gè)用戶定義的對(duì)象類型的實(shí)例或具有構(gòu)造函數(shù)的內(nèi)置對(duì)象類型之一,所以new Boolean返回的不是布爾值,而是內(nèi)置對(duì)象類型,詳情如下
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());
所以知道結(jié)果如下:
new Boolean(true)先調(diào)用對(duì)象的valueOf()方法返回true,再調(diào)用toString()方法輸出"true",得出字符串再調(diào)用Number()返回1
原理如上
3和4,這里不是比較,而是if判斷,因?yàn)榇嬖趯?duì)象,所以都是返回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三,關(guān)于z-index 層級(jí)樹(shù)
依據(jù)結(jié)構(gòu)樣式,給出例子里的層級(jí)先后順序,這里不太好說(shuō),大家直接看看原理再試一次看看吧
css中z-index層級(jí)
CSS z-index 屬性的使用方法和層級(jí)樹(shù)的概念
CSS基礎(chǔ)(七):z-index詳解
深入理解CSS定位中的堆疊z-index
我給一個(gè)顏色版給大家做參考
四,關(guān)于深入理解JS中的Function.prototype.bind()方法原理&兼容寫法
一般來(lái)說(shuō)我們想到改變函數(shù)this指向的方法無(wú)非就call、apply和bind;
方法名 | 描述 |
---|---|
call | 調(diào)用一個(gè)對(duì)象的一個(gè)方法,以另一個(gè)對(duì)象替換當(dāng)前對(duì)象,余參按順序傳遞 |
apply | 調(diào)用一個(gè)對(duì)象的一個(gè)方法,以另一個(gè)對(duì)象替換當(dāng)前對(duì)象,余參按數(shù)組傳遞 |
bind | 創(chuàng)建一個(gè)新函數(shù),稱為綁定函數(shù),當(dāng)調(diào)用這個(gè)綁定函數(shù)時(shí),綁定函數(shù)會(huì)以創(chuàng)建它時(shí)傳入 bind()方法的第一個(gè)參數(shù)作為 this,傳入 bind() 方法的第二個(gè)以及以后的參數(shù)加上綁定函數(shù)運(yùn)行時(shí)本身的參數(shù)按照順序作為原函數(shù)的參數(shù)來(lái)調(diào)用原函數(shù) |
他們之間的區(qū)別在于調(diào)用時(shí)機(jī)或者添加參數(shù)形式不同,所以我們可以用里面的方法做兼容.
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提供了一個(gè)兼容實(shí)現(xiàn)
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; }; }五,前端經(jīng)典面試題: 從輸入U(xiǎn)RL到頁(yè)面加載發(fā)生了什么?
親身遇到面試題之一,知識(shí)點(diǎn)太多了,贈(zèng)你飛機(jī)票前端經(jīng)典面試題: 從輸入U(xiǎn)RL到頁(yè)面加載發(fā)生了什么?
六,講解淺拷貝和深拷貝的區(qū)別并簡(jiǎn)單實(shí)現(xiàn)簡(jiǎn)單直觀點(diǎn)說(shuō):
淺拷貝只會(huì)將原對(duì)象的各個(gè)屬性進(jìn)行依次復(fù)制,而 JavaScript 存儲(chǔ)對(duì)象都是存地址的,所以淺復(fù)制會(huì)導(dǎo)致深層對(duì)象屬性都指向同一塊內(nèi)存地址;
深拷貝不僅將原對(duì)象的各個(gè)屬性進(jìn)行依次復(fù)制,而且將原對(duì)象的深層對(duì)象屬性也依次采用深拷貝的方法遞歸復(fù)制到新對(duì)象上;
(詳情可以參考我之前寫的文章關(guān)于javascript基本類型和引用類型小知識(shí))
淺拷貝簡(jiǎn)單實(shí)現(xiàn):function shallowCopy(obj) { var _obj = {}, key; //如果使用Object.keys更方便 for (key in obj) { //只復(fù)制對(duì)象本身首層屬性 if (obj.hasOwnProperty(key)) { _obj[key] = obj[key]; } } return _obj; } var obj1 = { a: 1, b: { c: 2, }, }, obj2; //復(fù)制對(duì)象并改變?cè)瓕?duì)象值 obj2 = shallowCopy(obj1); obj1.a = 2; obj1.b.c = 3; console.log(obj1, obj2);淺拷貝ES5實(shí)現(xiàn):
Object.assign() 方法用于將所有可枚舉屬性的值從一個(gè)或多個(gè)源對(duì)象復(fù)制到目標(biāo)對(duì)象。它將返回目標(biāo)對(duì)象
function shallowCopy(obj) { return Object.assign({}, obj); } var obj1 = { a: 1, b: { c: 2, }, }, obj2; //復(fù)制對(duì)象并改變?cè)瓕?duì)象值 obj2 = shallowCopy(obj1); obj1.a = 2; obj1.b.c = 3; console.log(obj1, obj2);深拷貝簡(jiǎn)單實(shí)現(xiàn):
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; //復(fù)制對(duì)象并改變?cè)瓕?duì)象值 obj2 = deepCopy(obj1); obj1.a = 2; obj1.b.c = 3; console.log(obj1, obj2);
注意:
for in遍歷性能太差,會(huì)包括對(duì)象原型鏈上的屬性,即使加上hasOwnProperty判斷也只是減少操作屬性;
有人會(huì)在遞歸使用arguments.callee方法,但是ECMAscript5 的 Strict Mode是禁止使用的,因?yàn)閍rguments是龐大且變化的Why was the arguments.callee.caller property deprecated in JavaScript?
函數(shù)名調(diào)用好處:
這個(gè)函數(shù)可以像其他任何代碼一樣在代碼中調(diào)用。
它不會(huì)污染名稱空間。
它的值不會(huì)改變。
它性能更好(訪問(wèn)參數(shù)對(duì)象是昂貴的)。
深拷貝轉(zhuǎn)格式寫法:序列化成JSON字符串的值會(huì)新開(kāi)一個(gè)存儲(chǔ)地址,從而分開(kāi)兩者關(guān)聯(lián);
function deepCopy(obj) { return JSON.parse(JSON.stringify(obj)); } var obj1 = { a: 1, b: { c: 2, }, }, obj2; //復(fù)制對(duì)象并改變?cè)瓕?duì)象值 obj2 = deepCopy(obj1); obj1.a = 2; obj1.b.c = 3; console.log(obj1, obj2);
注意:
只能處理能夠被 json 直接表示的數(shù)據(jù)結(jié)構(gòu)(Number, String, Boolean, Array, 扁平對(duì)象等),不支持NaN,Infinity,循環(huán)引用和function等;
如果構(gòu)造實(shí)例,會(huì)切斷原有對(duì)象的constructor等相關(guān)屬性;
深拷貝Object.create寫法:創(chuàng)建一個(gè)具有指定原型且可選擇性地包含指定屬性的對(duì)象
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; //復(fù)制對(duì)象并改變?cè)瓕?duì)象值 obj2 = deepCopy(obj1); obj1.a = 2; obj1.b.c = 3; console.log(obj1, obj2);
注意:
屬性不在復(fù)制對(duì)象本身屬性中,而在原型鏈上;
(詳情可以參考我之前寫的文章關(guān)于創(chuàng)建對(duì)象的三種寫法 ---- 字面量,new構(gòu)造器和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; //復(fù)制對(duì)象并改變?cè)瓕?duì)象值 obj2 = deepCopy(obj1); obj1.a = 2; obj1.b.c = 3; console.log(obj1, obj2);七,原生JS操作DOM?
說(shuō)多都是淚,從原生到JQ到MV*框架,老祖宗都模糊了.
查找節(jié)點(diǎn)方法 | 作用 |
---|---|
document.getElementById | 根據(jù)ID查找元素,大小寫敏感,如果有多個(gè)結(jié)果,只返回第一個(gè) |
document.getElementsByClassName | 根據(jù)類名查找元素,多個(gè)類名用空格分隔,返回一個(gè) HTMLCollection 。注意兼容性為IE9+(含)。另外,不僅僅是document,其它元素也支持 getElementsByClassName 方法 |
document.getElementsByTagName | 根據(jù)標(biāo)簽查找元素, * 表示查詢所有標(biāo)簽,返回一個(gè) HTMLCollection |
document.getElementsByName | 根據(jù)元素的name屬性查找,返回一個(gè) NodeList |
document.querySelector | 指定一個(gè)或多個(gè)匹配元素的 CSS 選擇器。 可以使用它們的 id, 類, 類型, 屬性, 屬性值等來(lái)選取元素。返回單個(gè)Node,IE8+(含),如果匹配到多個(gè)結(jié)果,只返回第一個(gè) |
document.querySelectorAll | 指定一個(gè)或多個(gè)匹配元素的 CSS 選擇器。 可以使用它們的 id, 類, 類型, 屬性, 屬性值等來(lái)選取元素。返回一個(gè) NodeList ,IE8+(含) |
document.forms | 獲取當(dāng)前頁(yè)面所有form,返回一個(gè) HTMLCollection |
方法 | 作用 |
---|---|
document.createElement | 創(chuàng)建元素 |
document.createTextNode | 創(chuàng)建文本 |
document.cloneNode | 克隆元素,接收一個(gè)bool參數(shù),用來(lái)表示是否復(fù)制子元素。 |
document.createDocumentFragment | 創(chuàng)建文檔碎片 |
document.createComment | 創(chuàng)建注釋節(jié)點(diǎn) |
//創(chuàng)建并插入文本 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);修改節(jié)點(diǎn)
方法 | 作用 |
---|---|
parent.appendChild(child) | 將child追加到parent的子節(jié)點(diǎn)的最后面 |
parentNode.insertBefore(newNode, refNode) | 將某個(gè)節(jié)點(diǎn)插入到另外一個(gè)節(jié)點(diǎn)的前面 |
parent.removeChild(child) | 刪除指定的子節(jié)點(diǎn)并返回子節(jié)點(diǎn) |
parent.replaceChild(child) | 將一個(gè)節(jié)點(diǎn)替換另一個(gè)節(jié)點(diǎn) |
parent.insertData(child) | 將數(shù)據(jù)插入已有的文本節(jié)點(diǎn)中 |
//創(chuàng)建并插入文本 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);八,解決計(jì)算精度問(wèn)題,例如0.1+0.2? toFixed()問(wèn)題:
返回的是字符串;
會(huì)強(qiáng)制保留限定小數(shù)位;
某些瀏覽器對(duì)于小數(shù)的進(jìn)位有點(diǎn)不同;
console.log((0.1 + 0.2).toFixed(2));網(wǎng)上流傳的方法,思路就是把數(shù)字轉(zhuǎn)換整數(shù)然后再除回原位數(shù):
/** * floatTool 包含加減乘除四個(gè)方法,能確保浮點(diǎn)數(shù)運(yùn)算不丟失精度 * * 我們知道計(jì)算機(jī)編程語(yǔ)言里浮點(diǎn)數(shù)計(jì)算會(huì)存在精度丟失問(wèn)題(或稱舍入誤差),其根本原因是二進(jìn)制和實(shí)現(xiàn)位數(shù)限制有些數(shù)無(wú)法有限表示 * 以下是十進(jìn)制小數(shù)對(duì)應(yīng)的二進(jìn)制表示 * 0.1 >> 0.0001 1001 1001 1001…(1001無(wú)限循環(huán)) * 0.2 >> 0.0011 0011 0011 0011…(0011無(wú)限循環(huán)) * 計(jì)算機(jī)里每種數(shù)據(jù)類型的存儲(chǔ)是一個(gè)有限寬度,比如 JavaScript 使用 64 位存儲(chǔ)數(shù)字類型,因此超出的會(huì)舍去。舍去的部分就是精度丟失的部分。 * * ** 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是否為一個(gè)整數(shù) */ function isInteger(obj) { return Math.floor(obj) === obj; } /* * 將一個(gè)浮點(diǎn)數(shù)轉(zhuǎn)成整數(shù),返回整數(shù)和倍數(shù)。如 3.14 >> 314,倍數(shù)是 100 * @param floatNum {number} 小數(shù) * @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; } /* * 核心方法,實(shí)現(xiàn)加減乘除運(yùn)算,確保不丟失精度 * 思路:把小數(shù)放大為整數(shù)(乘),進(jìn)行算術(shù)運(yùn)算,再縮小為小數(shù)(除) * * @param a {number} 運(yùn)算數(shù)1 * @param b {number} 運(yùn)算數(shù)2 * @param digits {number} 精度,保留的小數(shù)點(diǎn)數(shù),比如 2, 即保留為兩位小數(shù) * @param op {string} 運(yùn)算類型,有加減乘除(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) { // 兩個(gè)小數(shù)位數(shù)相同 result = n1 + n2; } else if (t1 > t2) { // o1 小數(shù)位 大于 o2 result = n1 + n2 * (t1 / t2); } else { // o1 小數(shù)位 小于 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"); })()); } } // 加減乘除的四個(gè)接口 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, }; })();九,遞歸閉包函數(shù)
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
因?yàn)椴粩噙f歸看起來(lái)會(huì)很迷糊,我們?cè)囍阉鸱殖霈F(xiàn)展示
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); } };
干擾代碼很多,但是實(shí)際上只有第一次執(zhí)行的時(shí)候會(huì)賦值給o,后續(xù)調(diào)用都只是改變n值
情況二var b = fun(0).fun(1).fun(2).fun(3); // undefined // 0 // 1 // 2
這個(gè)乍看之下和上面沒(méi)什么區(qū)別,但是結(jié)果挺詫異的,我也想了好久,還有一個(gè)糾結(jié)地方是我當(dāng)時(shí)不記得執(zhí)行方法和.運(yùn)算符誰(shuí)的優(yōu)先級(jí)比較高
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); } };
最后想起情況一其實(shí)是屬于閉包的用法,還涉及到執(zhí)行環(huán)境和作用域的知識(shí),因?yàn)樗菍儆趫?zhí)行完后只保存第一次變量o,所以除了第一次能夠正常賦值之后后續(xù)都是只賦值m,所以輸出結(jié)果都是0
(詳情可以參考我之前寫的文章Javascript難點(diǎn)知識(shí)運(yùn)用---遞歸,閉包,柯里化等(不定時(shí)更新))
情況二就比較特殊,因?yàn)樗麄€(gè)執(zhí)行環(huán)境運(yùn)行過(guò)程中都能夠訪問(wèn)改變o,具體還是得靠自己理解一下,我不知道怎么表達(dá)出來(lái)
var c = fun(0).fun(1) c.fun(2) c.fun(3) // undefined // 0 // 1 // 1
如果理解上面兩種情況的話,這題問(wèn)題就簡(jiǎn)單了.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/106410.html
摘要:和面試題盒子模型中盒子模型包括盒子模型和標(biāo)準(zhǔn)的盒子模型。客戶端錯(cuò)誤服務(wù)器無(wú)法理解請(qǐng)求的格式,客戶端不應(yīng)當(dāng)嘗試再次使用相同的內(nèi)容發(fā)起請(qǐng)求。服務(wù)器端暫時(shí)無(wú)法處理請(qǐng)求可能是過(guò)載或維護(hù)。 學(xué)而不思則罔,思而不學(xué)則殆。這一篇會(huì)將一些看到的面試題做一個(gè)總結(jié)。以后看到新的面試題也會(huì)持續(xù)的更新在這個(gè)里面。 HTML 和 CSS 面試題 盒子模型 CSS 中盒子模型包括 IE 盒子模型和標(biāo)準(zhǔn)的 W3C ...
摘要:責(zé)編現(xiàn)代化的方式開(kāi)發(fā)一個(gè)圖片上傳工具前端掘金對(duì)于圖片上傳,大家一定不陌生。之深入事件機(jī)制前端掘金事件綁定的方式原生的事件綁定方式有幾種想必有很多朋友說(shuō)種目前,在本人目前的研究中,只有兩種半兩種半還有半種的且聽(tīng)我道來(lái)。 Ajax 與數(shù)據(jù)傳輸 - 前端 - 掘金背景 在沒(méi)有ajax之前,前端與后臺(tái)傳數(shù)據(jù)都是靠表單傳輸,使用表單的方法傳輸數(shù)據(jù)有一個(gè)比較大的問(wèn)題就是每次提交數(shù)據(jù)都會(huì)刷新頁(yè)面,用...
閱讀 1389·2023-04-25 15:21
閱讀 2703·2021-11-24 10:23
閱讀 3432·2021-10-11 10:59
閱讀 3287·2021-09-03 10:28
閱讀 1758·2019-08-26 13:45
閱讀 2352·2019-08-26 12:11
閱讀 947·2019-08-26 12:00
閱讀 1744·2019-08-26 10:44