變量和類型

前端工程師吃飯的家伙峻村,深度麸折、廣度一樣都不能差。

一粘昨、JavaScript 規(guī)定了幾種語言類型

7 種基本數(shù)據(jù)類型:BigInt垢啼、Symbol、Undefined张肾、Null芭析、Boolean、Number和String

1 種復(fù)雜數(shù)據(jù)類型:Object

二吞瞪、JavaScript 對象的底層數(shù)據(jù)結(jié)構(gòu)是什么

通過 V8 的源碼嘗試分析 Object 的實現(xiàn):V8 里面所有的數(shù)據(jù)類型的根父類都是 Object馁启,Object 派生 HeapObject,提供存儲基本功能芍秆,往下的 JSReceiver 用于原型查找惯疙,再往下的 JSObject 就是 JS 里面的 Object,Array/Function/Date 等繼承于 JSObject妖啥。左邊的 FixedArray 是實際存儲數(shù)據(jù)的地方霉颠。



在創(chuàng)建一個 JSObject 之前,會先把讀到的 Object 的文本屬性序列化成 constant_properties迹栓,如下的 data:

var data = {
  name: 'yin',
  age: 18,
  '-school-': 'high school'
}

會被序列成:

../../v8/src/runtime/runtime-literals.cc 72 constant_properties:

0xdf9ed2aed19: [FixedArray]

– length: 6

  [0]: 0x1b5ec69833d1 <String[4]: name>

  [1]: 0xdf9ed2aec51 <String[3]: yin>

  [2]: 0xdf9ed2aec71 <String[3]: age>

  [3]: 18

  [4]: 0xdf9ed2aec91 <String[8]: -school->

  [5]: 0xdf9ed2aecb1 <String[11]: high school>

它是一個 FixedArray掉分,一共有 6 個元素,由于 data 總共是有 3 個屬性克伊,每個屬性有一個 key 和一個 value泥技,所以 Array 就有 6 個毅贮。第一個元素是第一個 key实辑,第二個元素是第一個 value浊洞,第三個元素是第二個 key,第四個元素是第二個 key犁跪,依次類推椿息。

Object 提供了一個 Print()的函數(shù),把它用來打印對象的信息非常有幫助坷衍。上面的輸出有兩種類型的數(shù)據(jù)寝优,一種是 String 類型,第二種是整型類型的枫耳。
FixedArray 是 V8 實現(xiàn)的一個類似于數(shù)組的類乏矾,它表示一段連續(xù)的內(nèi)存。

參考自:https://www.rrfed.com/2017/04/04/chrome-object/

三、Symbol 類型在實際開發(fā)中的應(yīng)用钻心、可手動實現(xiàn)一個簡單的 Symbol

應(yīng)用場景 1:使用 Symbol 來作為對象屬性名(key)

在這之前凄硼,我們通常定義或訪問對象的屬性時都是使用字符串,比如下面的代碼:

let obj = {
  abc: 123,
  hello: 'world'
}

obj['abc'] // 123
obj['hello'] // 'world'

而現(xiàn)在捷沸,Symbol 可同樣用于對象屬性的定義和訪問:

const PROP_NAME = Symbol()
const PROP_AGE = Symbol()

let obj = {
  [PROP_NAME]: '一斤代碼'
}
obj[PROP_AGE] = 18

obj[PROP_NAME] // '一斤代碼'
obj[PROP_AGE] // 18

隨之而來的是另一個非常值得注意的問題:就是當(dāng)使用了 Symbol 作為對象的屬性 key 后摊沉,在對該對象進(jìn)行 key 的枚舉時,會有什么不同痒给?在實際應(yīng)用中说墨,我們經(jīng)常會需要使用 Object.keys()或者 for...in 來枚舉對象的屬性名,那在這方面侈玄,Symbol 類型的 key 表現(xiàn)的會有什么不同之處呢婉刀?來看以下示例代碼:

let obj = {
  [Symbol('name')]: '一斤代碼',
  age: 18,
  title: 'Engineer'
}

Object.keys(obj) // ['age', 'title']

for (let p in obj) {
  console.log(p) // 分別會輸出:'age' 和 'title'
}

Object.getOwnPropertyNames(obj) // ['age', 'title']

由上代碼可知吟温,Symbol 類型的 key 是不能通過 Object.keys()或者 for...in 來枚舉的序仙,它未被包含在對象自身的屬性名集合(property names)之中。所以鲁豪,利用該特性潘悼,我們可以把一些不需要對外操作和訪問的屬性使用 Symbol 來定義。

也正因為這樣一個特性爬橡,當(dāng)使用 JSON.stringify()將對象轉(zhuǎn)換成 JSON 字符串的時候治唤,Symbol 屬性也會被排除在輸出內(nèi)容之外:

JSON.stringify(obj) // {"age":18,"title":"Engineer"}

我們可以利用這一特點來更好的設(shè)計我們的數(shù)據(jù)對象,讓“對內(nèi)操作”和“對外選擇性輸出”變得更加優(yōu)雅糙申。

然而宾添,這樣的話,我們就沒辦法獲取以 Symbol 方式定義的對象屬性了么柜裸?非也缕陕。還是會有一些專門針對 Symbol 的 API,比如:

// 使用Object的API
Object.getOwnPropertySymbols(obj) // [Symbol(name)]

// 使用新增的反射API
Reflect.ownKeys(obj) // [Symbol(name), 'age', 'title']

應(yīng)用場景 2:使用 Symbol 來替代常量

先來看一下下面的代碼疙挺,是不是在你的代碼里經(jīng)常會出現(xiàn)扛邑?

const TYPE_AUDIO = 'AUDIO'
const TYPE_VIDEO = 'VIDEO'
const TYPE_IMAGE = 'IMAGE'

function handleFileResource(resource) {
  switch (resource.type) {
    case TYPE_AUDIO:
      playAudio(resource)
      break
    case TYPE_VIDEO:
      playVideo(resource)
      break
    case TYPE_IMAGE:
      previewImage(resource)
      break
    default:
      throw new Error('Unknown type of resource')
  }
}

如上面的代碼中那樣,我們經(jīng)常定義一組常量來代表一種業(yè)務(wù)邏輯下的幾個不同類型铐然,我們通常希望這幾個常量之間是唯一的關(guān)系蔬崩,為了保證這一點,我們需要為常量賦一個唯一的值(比如這里的'AUDIO'搀暑、'VIDEO'沥阳、 'IMAGE'),常量少的時候還算好自点,但是常量一多桐罕,你可能還得花點腦子好好為他們?nèi)€好點的名字。

現(xiàn)在有了 Symbol,我們大可不必這么麻煩了:

const TYPE_AUDIO = Symbol()
const TYPE_VIDEO = Symbol()
const TYPE_IMAGE = Symbol()

這樣定義冈绊,直接就保證了三個常量的值是唯一的了侠鳄!是不是挺方便的呢。

應(yīng)用場景 3:使用 Symbol 定義類的私有屬性/方法

我們知道在 JavaScript 中死宣,是沒有如 Java 等面向?qū)ο笳Z言的訪問控制關(guān)鍵字 private 的伟恶,類上所有定義的屬性或方法都是可公開訪問的。因此這對我們進(jìn)行 API 的設(shè)計時造成了一些困擾毅该。

而有了 Symbol 以及模塊化機(jī)制博秫,類的私有屬性和方法才變成可能。例如:

在文件 a.js 中

const PASSWORD = Symbol()

class Login {
  constructor(username, password) {
    this.username = username
    this[PASSWORD] = password
  }

  checkPassword(pwd) {
    return this[PASSWORD] === pwd
  }
}

export default Login

在文件 b.js 中

import Login from './a'

const login = new Login('admin', '123456')

login.checkPassword('123456') // true

login.PASSWORD // oh!no!
login[PASSWORD] // oh!no!
login['PASSWORD'] // oh!no!

由于 Symbol 常量 PASSWORD 被定義在 a.js 所在的模塊中眶掌,外面的模塊獲取不到這個 Symbol挡育,也不可能再創(chuàng)建一個一模一樣的 Symbol 出來(因為 Symbol 是唯一的),因此這個 PASSWORD 的 Symbol 只能被限制在 a.js 內(nèi)部使用朴爬,所以使用它來定義的類屬性是沒有辦法被模塊外訪問到的即寒,達(dá)到了一個私有化的效果。

手動實現(xiàn) Symbol:

(function() {
    var root = this;

    var generateName = (function(){
        var postfix = 0;
        return function(descString){
            postfix++;
            return '@@' + descString + '_' + postfix
        }
    })()

    var SymbolPolyfill = function Symbol(description) {

        if (this instanceof SymbolPolyfill) throw new TypeError('Symbol is not a constructor');

        var descString = description === undefined ? undefined : String(description)

        var symbol = Object.create({
            toString: function() {
                return this.__Name__;
            },
            valueOf: function() {
                return this;
            }
        })

        Object.defineProperties(symbol, {
            '__Description__': {
                value: descString,
                writable: false,
                enumerable: false,
                configurable: false
            },
            '__Name__': {
                value: generateName(descString),
                writable: false,
                enumerable: false,
                configurable: false
            }
        });

        return symbol;
    }

    var forMap = {};

    Object.defineProperties(SymbolPolyfill, {
        'for': {
            value: function(description) {
                var descString = description === undefined ? undefined : String(description)
                return forMap[descString] ? forMap[descString] : forMap[descString] = SymbolPolyfill(descString);
            },
            writable: true,
            enumerable: false,
            configurable: true
        },
        'keyFor': {
            value: function(symbol) {
                for (var key in forMap) {
                    if (forMap[key] === symbol) return key;
                }
            },
            writable: true,
            enumerable: false,
            configurable: true
        }
    });

    root.SymbolPolyfill = SymbolPolyfill;


四召噩、JavaScript 中的變量在內(nèi)存中的具體存儲形式

棧內(nèi)存和堆內(nèi)存

JavaScript 中的變量分為基本類型和引用類型
基本類型是保存在棧內(nèi)存中的簡單數(shù)據(jù)段母赵,它們的值都有固定的大小,保存在椌叩危空間凹嘲,通過按值訪問

引用類型是保存在堆內(nèi)存中的對象,值大小不固定构韵,棧內(nèi)存中存放的該對象的訪問地址指向堆內(nèi)存中的對象周蹭,JavaScript 不允許直接訪問堆內(nèi)存中的位置,因此操作對象時疲恢,實際操作對象的引用

結(jié)合代碼與圖來理解

let a1 = 0 // 棧內(nèi)存
let a2 = 'this is string' // 棧內(nèi)存
let a3 = null // 棧內(nèi)存
let b = { x: 10 } // 變量b存在于棧中凶朗,{ x: 10 }作為對象存在于堆中
let c = [1, 2, 3] // 變量c存在于棧中,[1, 2, 3]作為對象存在于堆中

當(dāng)我們要訪問堆內(nèi)存中的引用數(shù)據(jù)類型時

  1. 從棧中獲取該對象的地址引用
  2. 再從堆內(nèi)存中取得我們需要的數(shù)據(jù)

基本類型發(fā)生復(fù)制行為

let a = 20
let b = a
b = 30
console.log(a) // 20

結(jié)合下面圖進(jìn)行理解:

在棧內(nèi)存中的數(shù)據(jù)發(fā)生復(fù)制行為時冈闭,系統(tǒng)會自動為新的變量分配一個新值俱尼,最后這些變量都是相互獨(dú)立互不影響的

引用類型發(fā)生復(fù)制行為

let a = { x: 10, y: 20 }
let b = a
b.x = 5
console.log(a.x) // 5
  1. 引用類型的復(fù)制,同樣為新的變量 b 分配一個新的值萎攒,保存在棧內(nèi)存中遇八,不同的是,這個值僅僅是引用類型的一個地址指針
  2. 他們兩個指向同一個值耍休,也就是地址指針相同刃永,在堆內(nèi)存中訪問到的具體對象實際上是同一個
  3. 因此改變 b.x 時,a.x 也發(fā)生了變化羊精,這就是引用類型的特性

結(jié)合下圖理解

總結(jié)

五斯够、基本類型對應(yīng)的內(nèi)置對象,以及他們之間的裝箱拆箱操作

JS 中的內(nèi)置函數(shù)(對象)

String()、Number()读规、Boolean()抓督、RegExp()、Date()束亏、Error()铃在、Array()、Function()碍遍、Object()定铜、symbol();類似于對象的構(gòu)造函數(shù)

1、這些內(nèi)置函數(shù)構(gòu)造的變量都是封裝了基本類型值的對象如:

var a = new String('abb') // typeof(a)=object

除了利用 Function()構(gòu)造的變量通過 typeof 輸出為 function 外其他均為 object

2怕敬、為了知道構(gòu)造的變量的真實類型可以利用:

Object.prototype.toString.call([1, 2, 3]) // "[object,array]"

后面的一個值即為傳入?yún)?shù)的類型

3揣炕、如果有常量形式(即利用基本數(shù)據(jù)類型)賦值給變量就不要用該方式來定義變量

裝箱

就是把基本類型轉(zhuǎn)變?yōu)閷?yīng)的對象。裝箱分為隱式和顯示

  • 隱式裝箱: 每當(dāng)讀取一個基本類型的值時东跪,后臺會創(chuàng)建一個該基本類型所對應(yīng)的對象畸陡。在這個基本類型上調(diào)用方法,其實是在這個基本類型對象上調(diào)用方法越庇。這個基本類型的對象是臨時的罩锐,它只存在于方法調(diào)用那一行代碼執(zhí)行的瞬間奉狈,執(zhí)行方法后立刻被銷毀卤唉。
let num = 123
num.toFixed(2) // '123.00'//上方代碼在后臺的真正步驟為
var c = new Number(123)
c.toFixed(2)
c = null

(1)創(chuàng)建一個 Number 類型的實例。

(2)在實例上調(diào)用方法仁期。

(3)銷毀實例桑驱。

  • 顯式裝箱: 通過內(nèi)置對象 Boolean、Object跛蛋、String 等可以對基本類型進(jìn)行顯示裝箱熬的。
var obj = new String('123')

拆箱

拆箱與裝箱相反,把對象轉(zhuǎn)變?yōu)榛绢愋偷闹瞪藜丁2鹣溥^程內(nèi)部調(diào)用了抽象操作 ToPrimitive 押框。該操作接受兩個參數(shù),第一個參數(shù)是要轉(zhuǎn)變的對象理逊,第二個參數(shù) PreferredType 是對象被期待轉(zhuǎn)成的類型橡伞。第二個參數(shù)不是必須的,默認(rèn)該參數(shù)為 number晋被,即對象被期待轉(zhuǎn)為數(shù)字類型

  • Number 轉(zhuǎn)化為對象

    1.先調(diào)用對象自身的 valueOf 方法兑徘。如果返回原始類型的值,則直接對該值使用 Number 函數(shù)羡洛,返回結(jié)果挂脑。

    2.如果 valueOf 返回的還是對象,繼續(xù)調(diào)用對象自身的 toString 方法。如果 toString 返回原始類型的值崭闲,則對該值使用 Number 函數(shù)肋联,返回結(jié)果。

    3.如果 toString 返回的還是對象刁俭,報錯牺蹄。

Number([1]); //1
轉(zhuǎn)換演變:
[1].valueOf(); // [1];
[1].toString(); // '1';Number('1'); //1
  • String 轉(zhuǎn)化為對象

    1.先調(diào)用對象自身的 toString 方法。如果返回原始類型的值薄翅,則對該值使用 String 函數(shù)沙兰,返回結(jié)果。

    2.如果 toString 返回的是對象翘魄,繼續(xù)調(diào)用 valueOf 方法鼎天。如果 valueOf 返回原始類型的值,則對該值使用 String 函數(shù)暑竟,返回結(jié)果斋射。

    3.如果 valueOf 返回的還是對象,報錯但荤。

String([1,2]) //"1,2"
轉(zhuǎn)化演變:
[1,2].toString();  //"1,2"
String("1,2");  //"1,2"
  • Boolean 轉(zhuǎn)化對象

    Boolean 轉(zhuǎn)換對象很特別罗岖,除了以下六個值轉(zhuǎn)換為 false,其他都為 true

undefined  null  false  0(包括+0和-0)  NaN  空字符串('')
Boolean(undefined) //false
Boolean(null)        //false
Boolean(false)       //false
Boolean(0)           //false
Boolean(NaN)         //false
Boolean('')          //false
 Boolean([]) //true
Boolean({})          //true
Boolean(new Date())  //true

六腹躁、理解值類型和引用類型

a 聲明變量時不同的內(nèi)存分配:

1)原始值:存儲在棧(stack)中的簡單數(shù)據(jù)段桑包,也就是說,它們的值直接存儲在變量訪問的位置纺非。

這是因為這些原始類型占據(jù)的空間是固定的哑了,所以可將他們存儲在較小的內(nèi)存區(qū)域 – 棧中。這樣存儲便于迅速查尋變量的值烧颖。

2)引用值:存儲在堆(heap)中的對象弱左,也就是說,存儲在變量處的值是一個指針(point)炕淮,指向存儲對象的內(nèi)存地址拆火。

這是因為:引用值的大小會改變,所以不能把它放在棧中涂圆,否則會降低變量查尋的速度们镜。相反,放在變量的棾俗郏空間中的值是該對象存儲在堆中的地址憎账。
地址的大小是固定的,所以把它存儲在棧中對變量性能無任何負(fù)面影響卡辰。

b 不同的內(nèi)存分配機(jī)制也帶來了不同的訪問機(jī)制

1)在 JS 中是不允許直接訪問保存在堆內(nèi)存中的對象的胞皱,所以在訪問一個對象時邪意,首先得到的是這個對象在堆內(nèi)存中的地址,然后再按照這個地址去獲得這個對象中的值反砌,這就是傳說中的按引用訪問雾鬼。

2)而原始類型的值則是可以直接訪問到的。

c 復(fù)制變量時的不同

1)原始值:在將一個保存著原始值的變量復(fù)制給另一個變量時宴树,會將原始值的副本賦值給新變量策菜,此后這兩個變量是完全獨(dú)立的,他們只是擁有相同的值而已酒贬,彼此都是獨(dú)立的又憨。

2)引用值:在將一個保存著對象內(nèi)存地址的變量復(fù)制給另一個變量時,會把這個內(nèi)存地址賦值給新變量锭吨,也就是說這兩個變量都指向了堆內(nèi)存中的同一個對象蠢莺,他們中任何一個作出的改變都會反映在另一個身上。(復(fù)制對象時并不會在堆內(nèi)存中新生成一個一模一樣的對象零如,只是多了一個保存指向這個對象指針的變量罷了)

d 參數(shù)傳遞的不同(把實參復(fù)制給形參的過程)

首先我們應(yīng)該明確一點:ECMAScript 中所有函數(shù)的參數(shù)都是按值來傳遞的躏将。
但是為什么涉及到原始類型與引用類型的值時仍然有區(qū)別呢?還不就是因為內(nèi)存分配時的差別考蕾。

1)原始值:只是把變量里的值傳遞給參數(shù)祸憋,之后參數(shù)和這個變量互不影響。

2)引用值:對象變量它里面的值是這個對象在堆內(nèi)存中的內(nèi)存地址肖卧,這一點你要時刻銘記在心蚯窥!

因此它傳遞的值也就是這個內(nèi)存地址,這也就是為什么函數(shù)內(nèi)部對這個參數(shù)的修改會體現(xiàn)在外部的原因了喜命,因為它們都指向同一個對象沟沙。

七、null 和 undefined 的區(qū)別

定義

Null 類型:Null 類型也只有一個特殊的值——null壁榕。從邏輯角度來看,null 值表示一個空對象指針赎瞎。

Undefined 類型:Undefined 類型只有一個值牌里,即特殊的 undefined。在使用 var 聲明變量但未對其加以初始化時务甥,這個變量的值就是 undefined牡辽。

null 和 undefined 的應(yīng)用場景

null 表示"沒有對象",即該處不應(yīng)該有值敞临。典型用法是:

(1) 作為函數(shù)的參數(shù)态辛,表示該函數(shù)的參數(shù)不是對象。

(2) 作為對象原型鏈的終點挺尿。

console.log(null instanceof Object) // false

undefined 表示"缺少值"奏黑,就是此處應(yīng)該有一個值炊邦,但是還沒有定義。典型用法是:

(1)變量被聲明了熟史,但沒有賦值時馁害,就等于 undefined。

(2) 調(diào)用函數(shù)時蹂匹,應(yīng)該提供的參數(shù)沒有提供碘菜,該參數(shù)等于 undefined。

(3)對象沒有賦值的屬性限寞,該屬性的值為 undefined忍啸。

(4)函數(shù)沒有返回值時,默認(rèn)返回 undefined履植。

Number 轉(zhuǎn)換的值

Number(null)輸出為 0, Number(undefined)輸出為 NaN

八吊骤、至少可以說出三種判斷 JavaScript 數(shù)據(jù)類型的方式,以及他們的優(yōu)缺點静尼,如何準(zhǔn)確的判斷數(shù)組類型

typeof

  • 適用場景

typeof操作符可以準(zhǔn)確判斷一個變量是否為下面幾個原始類型:

typeof 'ConardLi' // string
typeof 123 // number
typeof true // boolean
typeof Symbol() // symbol
typeof undefined // undefined

你還可以用它來判斷函數(shù)類型:

typeof function() {} // function
  • 不適用場景

    當(dāng)你用typeof來判斷引用類型時似乎顯得有些乏力了:

typeof [] // object
typeof {} // object
typeof new Date() // object
typeof /^\d*$/ // object

除函數(shù)外所有的引用類型都會被判定為object白粉。

另外typeof null === 'object'也會讓人感到頭痛,這是在JavaScript初版就流傳下來的bug鼠渺,后面由于修改會造成大量的兼容問題就一直沒有被修復(fù)...

instanceof

instanceof操作符可以幫助我們判斷引用類型具體是什么類型的對象:

;[] instanceof Array // true
new Date() instanceof Date // true
new RegExp() instanceof RegExp // true

我們先來回顧下原型鏈的幾條規(guī)則:

  • 1.所有引用類型都具有對象特性鸭巴,即可以自由擴(kuò)展屬性
  • 2.所有引用類型都具有一個__proto__(隱式原型)屬性,是一個普通對象
  • 3.所有的函數(shù)都具有prototype(顯式原型)屬性拦盹,也是一個普通對象
  • 4.所有引用類型__proto__值指向它構(gòu)造函數(shù)的prototype
  • 5.當(dāng)試圖得到一個對象的屬性時鹃祖,如果變量本身沒有這個屬性,則會去他的__proto__中去找

[] instanceof Array實際上是判斷Array.prototype是否在[]的原型鏈上普舆。
所以恬口,使用instanceof來檢測數(shù)據(jù)類型,不會很準(zhǔn)確沼侣,這不是它設(shè)計的初衷:

[] instanceof Object // true
function(){}  instanceof Object // true

另外祖能,使用instanceof也不能檢測基本數(shù)據(jù)類型,所以instanceof并不是一個很好的選擇蛾洛。

toString

上面我們在拆箱操作中提到了toString函數(shù)养铸,我們可以調(diào)用它實現(xiàn)從引用類型的轉(zhuǎn)換。

每一個引用類型都有toString方法轧膘,默認(rèn)情況下钞螟,toString()方法被每個Object對象繼承。如果此方法在自定義對象中未被覆蓋谎碍,toString() 返回 "[object type]"鳞滨,其中type是對象的類型。

const obj = {}
obj.toString() // [object Object]

注意蟆淀,上面提到了如果此方法在自定義對象中未被覆蓋拯啦,toString才會達(dá)到預(yù)想的效果澡匪,事實上,大部分引用類型比如Array提岔、Date仙蛉、RegExp等都重寫了toString方法。

我們可以直接調(diào)用Object原型上未被覆蓋的toString()方法碱蒙,使用call來改變this指向來達(dá)到我們想要的效果荠瘪。

jquery

我們來看看jquery源碼中如何進(jìn)行類型判斷:

var class2type = {};
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
function( i, name ) {
    class2type[ "[object " + name + "]" ] = name.toLowerCase();
} );

type: function( obj ) {
    if ( obj == null ) {
        return obj + "";
    }
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[Object.prototype.toString.call(obj) ] || "object" :
        typeof obj;
}

isFunction: function( obj ) {
        return jQuery.type(obj) === "function";
}

原始類型直接使用typeof,引用類型使用Object.prototype.toString.call取得類型赛惩。
判斷數(shù)組類型可以用 Array.isArray(value) 或者 Object.prototype.toString.call(value) 哀墓。

九、可能發(fā)生隱式類型轉(zhuǎn)換的場景以及轉(zhuǎn)換原則喷兼,應(yīng)如何避免或巧妙應(yīng)用

因為JavaScript是弱類型的語言篮绰,所以類型轉(zhuǎn)換發(fā)生非常頻繁,上面我們說的裝箱和拆箱其實就是一種類型轉(zhuǎn)換季惯。

類型轉(zhuǎn)換分為兩種吠各,隱式轉(zhuǎn)換即程序自動進(jìn)行的類型轉(zhuǎn)換,強(qiáng)制轉(zhuǎn)換即我們手動進(jìn)行的類型轉(zhuǎn)換勉抓。

強(qiáng)制轉(zhuǎn)換這里就不再多提及了贾漏,下面我們來看看讓人頭疼的可能發(fā)生隱式類型轉(zhuǎn)換的幾個場景,以及如何轉(zhuǎn)換:

類型轉(zhuǎn)換規(guī)則

如果發(fā)生了隱式轉(zhuǎn)換藕筋,那么各種類型互轉(zhuǎn)符合下面的規(guī)則:


if 語句和邏輯語句

if語句和邏輯語句中纵散,如果只有單個變量,會先將變量轉(zhuǎn)換為Boolean值隐圾,只有下面幾種情況會轉(zhuǎn)換成false伍掀,其余被轉(zhuǎn)換成true

null
undefined
;('')
NaN
0
false

各種運(yùn)數(shù)學(xué)算符

我們在對各種非Number類型運(yùn)用數(shù)學(xué)運(yùn)算符(- * /)時,會先將非Number類型轉(zhuǎn)換為Number類型;

1 - true // 0
1 - null //  1
1 * undefined //  NaN
2 * ['5'] //  10

注意+是個例外暇藏,執(zhí)行+操作符時:

  • 1.當(dāng)一側(cè)為String類型蜜笤,被識別為字符串拼接,并會優(yōu)先將另一側(cè)轉(zhuǎn)換為字符串類型叨咖。
  • 2.當(dāng)一側(cè)為Number類型瘩例,另一側(cè)為原始類型,則將原始類型轉(zhuǎn)換為Number類型甸各。
  • 3.當(dāng)一側(cè)為Number類型,另一側(cè)為引用類型焰坪,將引用類型和Number類型轉(zhuǎn)換成字符串后拼接趣倾。
123 + '123' // 123123   (規(guī)則1)
123 + null // 123    (規(guī)則2)
123 + true // 124    (規(guī)則2)
123 + {} // 123[object Object]    (規(guī)則3)

==

使用==時,若兩側(cè)類型相同某饰,則比較結(jié)果和===相同儒恋,否則會發(fā)生隱式轉(zhuǎn)換善绎,使用==時發(fā)生的轉(zhuǎn)換可以分為幾種不同的情況(只考慮兩側(cè)類型不同):

  • 1.NaN

    NaN和其他任何類型比較永遠(yuǎn)返回false(包括和他自己)。

NaN == NaN // false
  • 2.Boolean

    Boolean和其他任何類型比較诫尽,Boolean首先被轉(zhuǎn)換為Number類型禀酱。

true == 1 // true
true == '2' // false
true == ['1'] // true
true == ['2'] // false

這里注意一個可能會弄混的點:undefined、nullBoolean比較牧嫉,雖然undefined剂跟、nullfalse都很容易被想象成假值,但是他們比較結(jié)果是false酣藻,原因是false首先被轉(zhuǎn)換成0

undefined == false // false
null == false // false
  • 3.String 和 Number

    StringNumber比較曹洽,先將String轉(zhuǎn)換為Number類型。

123 == '123' // true
'' == 0 // true
  • 4.null 和 undefined

    null == undefined比較結(jié)果是true辽剧,除此之外送淆,null、undefined和其他任何結(jié)果的比較值都為false怕轿。

null == undefined // true
null == '' // false
null == 0 // false
null == false // false
undefined == '' // false
undefined == 0 // false
undefined == false // false
  • 5.原始類型和引用類型

    當(dāng)原始類型和引用類型做比較時偷崩,對象類型會依照ToPrimitive規(guī)則轉(zhuǎn)換為原始類型:

'[object Object]' == {} // true
'1,2,3' == [1, 2, 3] // true

來看看下面這個比較:

;[] == ![] // true

!的優(yōu)先級高于==![]首先會被轉(zhuǎn)換為false撞羽,然后根據(jù)上面第三點阐斜,false轉(zhuǎn)換成Number類型0,左側(cè)[]轉(zhuǎn)換為0放吩,兩側(cè)比較相等智听。

;([null] == false[undefined]) == // true
  false // true

根據(jù)數(shù)組的ToPrimitive規(guī)則,數(shù)組元素為nullundefined時渡紫,該元素被當(dāng)做空字符串處理到推,所以[null]、[undefined]都會被轉(zhuǎn)換為0惕澎。
所以莉测,說了這么多,推薦使用===來判斷兩個值是否相等...

一道有意思的面試題

一道經(jīng)典的面試題唧喉,如何讓:a == 1 && a == 2 && a == 3捣卤。
根據(jù)上面的拆箱轉(zhuǎn)換,以及==的隱式轉(zhuǎn)換八孝,我們可以輕松寫出答案:

const a = {
  value: [3, 2, 1],
  valueOf: function() {
    return this.value.pop()
  }
}

十董朝、出現(xiàn)小數(shù)精度丟失的原因,JavaScript 可以存儲的最大數(shù)字干跛、最大安全數(shù)字子姜,JavaScript 處理大數(shù)字的方法、避免精度丟失的方法

出現(xiàn)小數(shù)精度丟失的原因

計算機(jī)的二進(jìn)制實現(xiàn)和位數(shù)限制有些數(shù)無法有限表示楼入。就像一些無理數(shù)不能有限表示哥捕,如 圓周率 3.1415926...牧抽,1.3333... 等。JS 遵循 IEEE 754 規(guī)范遥赚,采用雙精度存儲(double precision)扬舒,占用 64 bit。如圖

意義

1 位用來表示符號位

11 位用來表示指數(shù)

52 位表示尾數(shù)

浮點數(shù)凫佛,比如

1

2

0.1 >> 0.0001 1001 1001 1001…(1001 無限循環(huán))

0.2 >> 0.0011 0011 0011 0011…(0011 無限循環(huán))

此時只能模仿十進(jìn)制進(jìn)行四舍五入了讲坎,但是二進(jìn)制只有 0 和 1 兩個,于是變?yōu)?0 舍 1 入御蒲。這即是計算機(jī)中部分浮點數(shù)運(yùn)算時出現(xiàn)誤差衣赶,丟失精度的根本原因。
JS 的最大和最小安全值可以這樣獲得:

console.log(Number.MAX_SAFE_INTEGER) //9007199254740991
console.log(Number.MIN_SAFE_INTEGER) //-9007199254740991

對于整數(shù)厚满,前端出現(xiàn)問題的幾率可能比較低府瞄,畢竟很少有業(yè)務(wù)需要需要用到超大整數(shù),只要運(yùn)算結(jié)果不超過 Math.pow(2, 53) 就不會丟失精度碘箍。如果實在是超過最大安全數(shù)字了遵馆,那就用 BigInt(Number)計算。

對于小數(shù)丰榴,前端出現(xiàn)問題的幾率還是很多的货邓,尤其在一些電商網(wǎng)站涉及到金額等數(shù)據(jù)。解決方式:把小數(shù)放到位整數(shù)(乘倍數(shù))四濒,再縮小回原來倍數(shù)(除倍數(shù))换况,也就是說,盡量在業(yè)務(wù)中避免處理小數(shù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盗蟆,一起剝皮案震驚了整個濱河市戈二,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌喳资,老刑警劉巖觉吭,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異仆邓,居然都是意外死亡鲜滩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進(jìn)店門节值,熙熙樓的掌柜王于貴愁眉苦臉地迎上來徙硅,“玉大人,你說我怎么就攤上這事搞疗∶朴危” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵贴汪,是天一觀的道長脐往。 經(jīng)常有香客問我,道長扳埂,這世上最難降的妖魔是什么业簿? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮阳懂,結(jié)果婚禮上梅尤,老公的妹妹穿的比我還像新娘。我一直安慰自己岩调,他們只是感情好巷燥,可當(dāng)我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著号枕,像睡著了一般缰揪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上葱淳,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天钝腺,我揣著相機(jī)與錄音,去河邊找鬼赞厕。 笑死艳狐,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的皿桑。 我是一名探鬼主播毫目,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诲侮!你這毒婦竟也來了镀虐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤浆西,失蹤者是張志新(化名)和其女友劉穎粉私,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體近零,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诺核,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了久信。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窖杀。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖裙士,靈堂內(nèi)的尸體忽然破棺而出入客,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布桌硫,位于F島的核電站夭咬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铆隘。R本人自食惡果不足惜卓舵,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望膀钠。 院中可真熱鬧掏湾,春花似錦、人聲如沸肿嘲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雳窟。三九已至尊浪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涩拙,已是汗流浹背际长。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留兴泥,地道東北人工育。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像搓彻,于是被迫代替她去往敵國和親如绸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容