午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

JavaScript 新手的踩坑日記

 火騎士大大 2017-06-19

引語

在1995年5月,Eich 大神在10天內(nèi)就寫出了第一個(gè)腳本語言的版本,JavaScript 的第一個(gè)代號(hào)是 Mocha,Marc Andreesen 起的這個(gè)名字。由于商標(biāo)問題以及很多產(chǎn)品已經(jīng)使用了 Live 的前綴,網(wǎng)景市場部將它改名為 LiveScript。在1995年11月底,Navigator 2.0B3 發(fā)行,其中包含了該語言的原型,這個(gè)版本相比之前沒有什么大的變化。在1995年12月初,Java 語言發(fā)展壯大,Sun 把 Java 的商標(biāo)授權(quán)給了網(wǎng)景。這個(gè)語言被再次改名,變成了最終的名字——JavaScript。在之后的1997年1月,標(biāo)準(zhǔn)化以后,就成為現(xiàn)在的 ECMAScript。

近一兩年在客戶端上用到 JS 的地方也越來越多了,筆者最近接觸了一下 JS ,作為前端小白,記錄一下近期自己“踩坑”的成長經(jīng)歷。

一. 原始值和對象

在 JavaScript 中,對值的區(qū)分就兩種:

  • 1.原始值:BOOL,Number,String,null,undefined。

  • 2.對象:每個(gè)對象都有唯一的標(biāo)識(shí)且只嚴(yán)格的等于(===)自己。

null,undefined沒有屬性,連toString( )方法也沒有。

false,0,NaN,undefined,null,' ' ,都是false。

typeof 運(yùn)算符能區(qū)分原始值和對象,并檢測出原始值的類型。

instanceof 運(yùn)算符可以檢測出一個(gè)對象是否是特定構(gòu)造函數(shù)的一個(gè)實(shí)例或者是否為它的一個(gè)子類。

null 返回的是一個(gè) object,這個(gè)是一個(gè)不可修復(fù)的 bug,如果修改這個(gè) bug,就會(huì)破壞現(xiàn)有代碼體系。但是這不能表示 null 是一個(gè)對象。

因?yàn)榈谝淮?JavaScript 引擎中的 JavaScript 值表示為32位的字符。最低3位作為一種標(biāo)識(shí),表示值是對象,整數(shù),浮點(diǎn)數(shù)或者布爾值。對象的標(biāo)識(shí)是000,而為了表現(xiàn) null ,引擎使用了機(jī)器語言 NULL 的指針,該字符的所有位都是0。而 typeof 就是檢測值的標(biāo)志位,這就是為什么它會(huì)認(rèn)為 null 是一個(gè)對象了。

所以判斷 一個(gè) value 是不是一個(gè)對象應(yīng)該按照如下條件判斷:

function isObject (value) { return ( value !== null && (typeof value === 'object' || typeof value === 'function'));}

null 是原型鏈最頂端的元素

Object.getPrototypeOf(Object.prototype)<>null

判斷 undefined 和 null 可以用嚴(yán)格相等判斷:

if(x === null) { // 判斷是否為 null}if (x === undefined) { // 判斷是否為 undefined}if (x === void 0 ) { // 判斷是否為 undefined,void 0 === undefined}if (x != null ) { // 判斷x既不是undefined,也不是null // 這種寫法等價(jià)于 if (x !== undefined && x !== null )}

在原始值里面有一個(gè)特例,NaN 雖然是原始值,但是它和它本身是不相等的。

NaN === NaN

原始值的構(gòu)造函數(shù) Boolean,Number,String 可以把原始值轉(zhuǎn)換成對象,也可以把對象轉(zhuǎn)換成原始值。

// 原始值轉(zhuǎn)換成對象var object = new String('abc')// 對象轉(zhuǎn)換成原始值String(123)'123'

但是在對象轉(zhuǎn)換成原始值的時(shí)候,需要注意一點(diǎn):如果用 valueOf() 函數(shù)進(jìn)行轉(zhuǎn)換的時(shí)候,轉(zhuǎn)換一切正確。

new Boolean(true).valueOf()

但是使用構(gòu)造函數(shù)將包裝對象轉(zhuǎn)換成原始值的時(shí)候,BOOL值是不能正確被轉(zhuǎn)換的。

Boolean(new Boolean(false))

構(gòu)造函數(shù)只能正確的提取出包裝對象中的數(shù)字和字符串。

二. 寬松相等帶來的bug

在 JavaScript 中有兩種方式來判斷兩個(gè)值是否相等。

  • 嚴(yán)格相等 ( === ) 和嚴(yán)格不等 ( !== ) 要求比較的值必須是相同的類型。

  • 寬松相等 ( == ) 和寬松不等 ( != ) 會(huì)先嘗試將兩個(gè)不同類型的值進(jìn)行轉(zhuǎn)換,然后再使用嚴(yán)格等進(jìn)行比較。

寬松相等就會(huì)遇到一些bug:

undefined == null // undefinednull 是寬松相等的

關(guān)于嚴(yán)格相等( Strict equality ) 和 寬松相等( Loose equality ),GitHub上有一個(gè)人總結(jié)了一張圖,挺好的,貼出來分享一下,Github地址在 這里

但是如果用 Boolean( ) 進(jìn)行轉(zhuǎn)換的時(shí)候情況又有不同:

這里為何對象總是為true ?

在 ECMAScript 1中,曾經(jīng)規(guī)定不支持通過對象配置來轉(zhuǎn)換(比如 toBoolean() 方法)。原理是布爾運(yùn)算符 || 和 && 會(huì)保持運(yùn)算數(shù)的值。因此,如果鏈?zhǔn)绞褂眠@些運(yùn)算符,會(huì)多次確認(rèn)相同值的真假。這樣的檢查對于原始值類型成本不大,但是對于對象,如果能通過配置來轉(zhuǎn)換布爾值,成本很大。所以從 ECMAScript 1 開始,對象總是為 true 來避免了這些成本轉(zhuǎn)換。

三. Number

JavaScript 中所有的數(shù)字都只有一種類型,都被當(dāng)做浮點(diǎn)數(shù),JavaScript 內(nèi)部會(huì)做優(yōu)化,來區(qū)分浮點(diǎn)數(shù)組和整數(shù)。JavaScript 的數(shù)字是雙精度的(64位),基于 IEEE 754 標(biāo)準(zhǔn)。

由于所有數(shù)字都是浮點(diǎn)數(shù),所以這里就會(huì)有精度的問題。還記得前段時(shí)間網(wǎng)上流傳的機(jī)器人的漫畫么?

精度的問題就會(huì)引發(fā)一些奇妙的事情

0.1 + 0.2 ; // 0.300000000000004( 0.1 + 0.2 ) + 0.3; // 0.60000000000010.1 + ( 0.2 + 0.3 ); // 0.6(0.8+0.7+0.6+0.5) / 4 // 0.65(0.6+0.7+0.8+0.5) / 4 // 0.6499999999999999

變換一個(gè)位置,加一個(gè)括號(hào),都會(huì)影響精度。為了避免這個(gè)問題,建議還是轉(zhuǎn)換成整數(shù)。

( 8 + 7 + 6 + 5) / 4 / 10 ; // 0.65( 6 + 8 + 5 + 7) / 4 / 10 ; // 0.65

在數(shù)字里面有4個(gè)特殊的數(shù)值:

  • 2個(gè)錯(cuò)誤值:NaN 和 Infinity

  • 2個(gè)0,一個(gè)+0,一個(gè)-0。0是會(huì)帶正號(hào)和負(fù)號(hào)。因?yàn)檎?fù)號(hào)和數(shù)值是分開存儲(chǔ)的。

typeof NaN'number'

(吐槽:NaN 是 “ not a number ”的縮寫,但是它卻是一個(gè)數(shù)字)

NaN 是 JS 中唯一一個(gè)不能自身嚴(yán)格相等的值:

NaN === NaN

所以不能通過 Array.prototype.indexOf 方法去查找 NaN (因?yàn)閿?shù)組的 indexOf 方法會(huì)進(jìn)行嚴(yán)格等的判斷)。

[ NaN ].indexOf( NaN )<>1

正確的姿勢有兩種:

  • 第一種:

function realIsNaN( value ){ return typeof value === 'number' && isNaN(value);}

上面這種之所以需要判斷類型,是因?yàn)樽址D(zhuǎn)換會(huì)先轉(zhuǎn)換成數(shù)字,轉(zhuǎn)換失敗為 NaN。所以和 NaN 相等。

isNaN( 'halfrost' )
  • 第二種方法是利用 IEEE 754 標(biāo)準(zhǔn)里面的定義,NaN 和任意值比較,包括和自身進(jìn)行比較,都是無序的

function realIsNaN( value ){ return value !== value ;}

另外一個(gè)錯(cuò)誤值 Infinity 是由表示無窮大,或者除以0導(dǎo)致的。

判斷它直接用 寬松相等 == ,或者嚴(yán)格相等 === 判斷即可。

但是 isFinite() 函數(shù)不是專門用來判斷Infinity的,是用來判斷一個(gè)值是否是錯(cuò)誤值(這里表示既不是 NaN,又不是 Infinity,排除掉這兩個(gè)錯(cuò)誤值)。

在 ES6 中 引入了兩個(gè)函數(shù)專門判斷 Infinity 和 NaN的,Number.isFinite() 和 Number.isNaN() 以后都建議用這兩個(gè)函數(shù)進(jìn)行判斷。

JS 中整型是有一個(gè)安全區(qū)間,在( -2^53 , 2^53)之間。所以如果數(shù)字超過了64位無符號(hào)的整型數(shù)字,就只能用字符串進(jìn)行存儲(chǔ)了。

利用 parseInt() 進(jìn)行轉(zhuǎn)換成數(shù)字的時(shí)候,會(huì)有出錯(cuò)的時(shí)候,結(jié)果不可信:

parseInt(1000000000000000000000000000.99999999999999999,10)<>

parseInt( str , redix? ) 會(huì)先把第一個(gè)參數(shù)轉(zhuǎn)換成字符串:

String(1000000000000000000000000000.99999999999999999)'1e+27'

parseInt 不認(rèn)為 e 是整數(shù),所以在 e 之后的就停止解析了,所以最終輸出1。

JS 中的 % 求余操作符并不是我們平時(shí)認(rèn)為的取模。

-9%7<>2

求余操作符會(huì)返回一個(gè)和第一個(gè)操作數(shù)相同符號(hào)的結(jié)果。取模運(yùn)算是和第二個(gè)操作數(shù)符號(hào)相同。

所以比較坑的就是我們平時(shí)判斷一個(gè)數(shù)是否是奇偶數(shù)的問題就會(huì)出現(xiàn)錯(cuò)誤:

function isOdd( value ){ return value % 2 === 1;}console.log(-3); // falseconsole.log(-2); // false

正確姿勢是:

function isOdd( value ){ return Math.abs( value % 2 ) === 1;}console.log(-3); // trueconsole.log(-2); // false

四. String

字符串比較符,是無法比較變音符和重音符的。

'?' <>'b' <>'b'

五. Array

創(chuàng)建數(shù)組的時(shí)候不能用單個(gè)數(shù)字創(chuàng)建數(shù)組。

new Array(2) // 這里的一個(gè)數(shù)字代表的是數(shù)組的長度<[ ,="" ,="">new Array(2,3,4)<>2,3,4]

刪除元素會(huì)刪出空格,但是不會(huì)改變數(shù)組的長度。

var array = [1,2,3,4]array.length4delete array[1]array<>1, ,3,4]array.length4

所以這里的刪除不是很符合我們之前的刪除,正確姿勢是用splice

var array = [1,2,3,4,56,7,8,9]array.splice(1,3)array<>1, 56, 7, 8, 9]array.length5

針對數(shù)組里面的空缺,不同的遍歷方法行為不同

在 ES5 中:

在 ES6 中:規(guī)定,遍歷時(shí)不跳過空缺,空缺都轉(zhuǎn)化為undefined

六. Set 、Map、WeakSet、WeakMap

七. 循環(huán)

先說一個(gè) for-in 的坑:

var scores = [ 11,22,33,44,55,66,77 ];var total = 0;for (var score in scores) { total += score;}var mean = total / scores.length;mean;

一般人看到這道題肯定就開始算了,累加,然后除以7 。那么這題就錯(cuò)了,如果把數(shù)組里面的元素變的更加復(fù)雜:

var scores = [ 1242351,252352,32143,452354,51455,66125,74217 ];

其實(shí)這里答案和數(shù)組里面元素是多少無關(guān)。只要數(shù)組元素個(gè)數(shù)是7,最終答案都是17636.571428571428。

原因是 for-in 循環(huán)的是數(shù)組下標(biāo),所以 total = ‘00123456’ ,然后這個(gè)字符串再除以7。

遍歷對象的屬性,ES6 中有6種方法:

八. 隱式轉(zhuǎn)換 / 強(qiáng)制轉(zhuǎn)換 帶來的bug

var formData = { width : '100'};var w = formData.width;var outer = w + 20;console.log( outer === 120 ); // false;console.log( outer === '10020'); // true

九. 運(yùn)算符重載

在 JavaScript 無法重載或者自定義運(yùn)算符,包括等號(hào)。

十. 函數(shù)聲明和變量聲明的提升

先舉一個(gè)函數(shù)提升的例子。

function foo() { bar(); function bar() { …… }}

var 變量也具有提升的特性。但是把函數(shù)賦值給變量以后,提升的效果就會(huì)消失。

function foo() { bar(); // error! var bar = function () { …… }}

上述函數(shù)就沒有提升效果了。

函數(shù)聲明是做了完全提升,變量聲明只是做了部分提升。變量的聲明才有提升的作用,賦值的過程并不會(huì)提升。

JavaScript 支持詞法作用域( lexical scoping ),即除了極少的例外,對變量 foo 的引用會(huì)被綁定到聲明 foo 變量最近的作用域中。ES5中 不支持塊級(jí)作用域,即變量定義的作用域并不是離其最近的封閉語句或代碼塊,而包含它們的函數(shù)。所有的變量聲明都會(huì)被提升,聲明會(huì)被移動(dòng)到函數(shù)的開始處,而賦值則仍然會(huì)在原來的位置進(jìn)行。

function foo() { var x = -10; if ( x <>0) { var tmp = -x; …… } console.log(tmp); // 10}

這里 tmp 就有變量提升的效果。

再舉個(gè)例子:

foo = 2;var foo; console.log( foo );

上面這個(gè)例子還是輸出2,不是輸出undefined。

這個(gè)經(jīng)過編譯器編譯以后,其實(shí)會(huì)變成下面這個(gè)樣子:

var foo; foo = 2;console.log( foo );

變量聲明被提前了,賦值還在原地。 為了加深一下這句話的理解,再舉一個(gè)例子:

console.log( a ); var a = 2;

上述代碼會(huì)被編譯成下面的樣子:

var foo;console.log( foo ); foo = 2;

所以輸出的是undefined。

如果變量和函數(shù)都存在提升的情況, 那么函數(shù)提升優(yōu)先級(jí)更高 。

foo(); // 1var foo;function foo() { console.log( 1 );}foo = function() { console.log( 2 );};

上面經(jīng)過編譯過會(huì)變成下面這樣子:

function foo() { console.log( 1 );}foo(); // 1foo = function() { console.log( 2 );};

最終結(jié)果輸出是1,不是2 。這就說明了函數(shù)提升是優(yōu)先于變量提升的。

為了避免變量提升,ES6中引入了 let 和 const 關(guān)鍵字,使用這兩個(gè)關(guān)鍵字就不會(huì)有變量提升了。原理是,在代碼塊內(nèi),使用 let 命令聲明變量之前,該變量都是不可用的,這塊區(qū)域叫“暫時(shí)性死區(qū)”(temporal dead zone,TDZ)。TDZ 的做法是,只要一進(jìn)入到這一區(qū)域,所要使用的變量就已經(jīng)存在了,變量還是“提升”了,但是不能獲取,只有等到聲明變量的那一行出現(xiàn),才可以獲取和使用該變量。

ES6 的這種做法也給 JS 帶來了塊級(jí)作用域,(在 ES5 中只有全局作用于和函數(shù)作用域),于是立即執(zhí)行匿名函數(shù)(IIFE)就不在必要了。

十一. arguments 不是數(shù)組

arguments 不是數(shù)組,它只是類似于數(shù)組。它有l(wèi)ength屬性,可以通過方括號(hào)去訪問它的元素。不能移除它的元素,也不能對它調(diào)用數(shù)組的方法。

不要在函數(shù)體內(nèi)使用 arguments 變量,使用 rest 運(yùn)算符( ... )代替。因?yàn)?rest 運(yùn)算符顯式表明了你想要獲取的參數(shù),而且 arguments 僅僅只是一個(gè)類似的數(shù)組,而 rest 運(yùn)算符提供的是一個(gè)真正的數(shù)組。

下面有一個(gè)把 arguments 當(dāng)數(shù)組用的例子:

function callMethod(obj,method) { var shift = [].shift; shift.call(arguments); shift.call(arguments); return obj[method].apply(obj,arguments);}var obj = { add:function(x,y) { return x + y ;}};callMethod(obj,'add',18,38);

上述代碼直接報(bào)錯(cuò):

Uncaught TypeError: Cannot read property 'apply' of undefined at callMethod (:5:21) at:12:1

出錯(cuò)的原因就在于 arguments 并不是函數(shù)參數(shù)的副本,所有命名參數(shù)都是 arguments 對象中對應(yīng)索引的別名。因此通過 shift 方法移除 arguments 對象中的元素之后,obj 仍然是 arguments[0] 的別名,method 仍然是 arguments[1] 的別名。看上去是在調(diào)用 obj[add],實(shí)際上是在調(diào)用17[25]。

還有一個(gè)問題,使用 arguments 引用的時(shí)候。

function values() { var i = 0 , n = arguments.length; return { hasNext: function() { return i < n;="" },="">next: function() { if (i >= n) { throw new Error('end of iteration'); } return arguments[i++]; } }}var it = values(1,24,53,253,26,326,);it.next(); // undefinedit.next(); // undefinedit.next(); // undefined

上述代碼是想構(gòu)造一個(gè)迭代器來遍歷 arguments 對象的元素。這里之所以會(huì)輸出 undefined,是因?yàn)橛幸粋€(gè)新的 arguments 變量被隱式的綁定到了每個(gè)函數(shù)體內(nèi),每個(gè)迭代器 next 方法含有自己的 arguments 變量,所以執(zhí)行 it.next 的參數(shù)時(shí),已經(jīng)不是 values 函數(shù)中的參數(shù)了。

更改方式也簡單,只要聲明一個(gè)局部變量,next 的時(shí)候能引用到這個(gè)變量即可。

function values() { var i = 0 , n = arguments.length,a = arguments; return { hasNext: function() { return i < n;="" },="">next: function() { if (i >= n) { throw new Error('end of iteration'); } return a[i++]; } }}var it = values(1,24,53,253,26,326,);it.next(); // 1it.next(); // 24it.next(); // 53

十二. IIFE 引入新的作用域

在 ES5 中 IIFE 是為了解決 JS 缺少塊級(jí)作用域,但是到了 ES6 中,這個(gè)就可以不需要了。

十三. 函數(shù)中 this 的問題

在嵌套函數(shù)中不能訪問方法中的 this 變量。

var halfrost = { name:'halfrost', friends: [ 'haha' , 'hehe' ], sayHiToFriends: function() { 'use strict'; this.friends.forEach(function (friend) { // 'this' is undefined here console.log(this.name + 'say hi to' + friend); }); }}halfrost.sayHiToFriends()

這時(shí)就會(huì)出現(xiàn)一個(gè)TypeError: Cannot read property 'name' of undefined。

解決這個(gè)問題有兩種方法:

  • 第一種:將 this 保存在變量中。

sayHiToFriends: function() { 'use strict'; var that = this; this.friends.forEach(function (friend) { console.log(that.name + 'say hi to' + friend); });}
  • 第二種:利用bind()函數(shù)

使用bind()給回調(diào)函數(shù)的this綁定固定值,即函數(shù)的this

sayHiToFriends: function() { 'use strict'; this.friends.forEach(function (friend) { console.log(this.name + 'say hi to' + friend); }.bind(this));}
  • 第三種:利用 forEach 的第二個(gè)參數(shù),把 this 指定一個(gè)值。

sayHiToFriends: function() { 'use strict'; this.friends.forEach(function (friend) { console.log(this.name + 'say hi to' + friend); }, this);}

到了 ES6 里面,建議能用箭頭函數(shù)的地方用箭頭函數(shù)。

簡單的,單行的,不會(huì)復(fù)用的函數(shù),都建議用箭頭函數(shù),如果函數(shù)體很復(fù)雜,行數(shù)很多,還應(yīng)該用傳統(tǒng)寫法。

箭頭函數(shù)里面的 this 對象就是定義時(shí)候的對象,而不是使用時(shí)候的對象,這里存在“綁定關(guān)系”。

這里的“綁定”機(jī)制并不是箭頭函數(shù)帶來的,而是因?yàn)榧^函數(shù)根本就沒有自己的 this,導(dǎo)致內(nèi)部的 this 就是外層代碼塊的 this,正因?yàn)檫@個(gè)特性,也導(dǎo)致了以下的情況都不能使用箭頭函數(shù):

  • 不能當(dāng)做構(gòu)造函數(shù),不能使用 new 命令,因?yàn)闆]有 this,否則會(huì)拋出一個(gè)錯(cuò)誤。

  • 不可以使用 argument 對象,該對象在函數(shù)體內(nèi)不存在,非要使用就只能用 rest 參數(shù)代替。也不能使用 super,new.target 。

  • 不可以使用 yield 命令,不能作為 Generator 函數(shù)。

  • 不可以使用call(),apply(),bind()這些方法改變 this 的指向。

十四. 異步

異步編程有以下幾種:

  • 回調(diào)函數(shù)callback

  • 事件監(jiān)聽

  • 發(fā)布 / 訂閱

  • Promise對象

  • Async / Await

(這個(gè)日記可能一直未完待續(xù)......)

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多