林大媽的JavaScript進階知識(一):對象與記憶體

来源:https://www.cnblogs.com/BlogOfMotherLyn/archive/2020/02/14/12307328.html

JavaScript中的基本數據類型 在JS中,有6種基本數據類型: 1. string 2. number 3. boolean 4. null 5. undefined 6. Symbol(ES6) 除去這六種基本數據類型以外,其他的所有變數數據類型都是Object。基本類型的操作在JS底層中是 ...


JavaScript中的基本數據類型

在JS中,有6種基本數據類型:

  1. string
  2. number
  3. boolean
  4. null
  5. undefined
  6. Symbol(ES6)

除去這六種基本數據類型以外,其他的所有變數數據類型都是Object。基本類型的操作在JS底層中是這樣實現的:

// 1. 申請一塊記憶體,存儲foo變數的內容為1
let foo = 1
// 2. 定義foo為1時,foo的數據類型是number
typeof foo // "number"
// 3. 我們知道,const的意思是constant(常量,無法改變的)
const bar = foo
// 4. 修改值時,新申請了一塊記憶體存儲foo的內容為2
foo = 2
// 4. 則會發現,foo已經是2了,bar仍然是1
console.log(foo) // 2
console.log(bar) // 1
由此可見,我們定義的變數實際上都是指針。基本數據類型的修改實際上是新申請一塊記憶體地址,將這個指針指向新的記憶體地址。使用const定義變數,實際上相當於定義了一個指針常量,指向固定的地址不能被修改。

JavaScript中的對象

定義和修改對象

我們來試著從變數定義的執行結果看出它在底層的執行方式:

// 1. 定義一個對象
const obj = {
    foo: 1
}
// 2. 定義一個新變數與其相等
const anotherObj = obj
// 3. 修改這個對象
obj.foo = 2
// 4. 發現兩個對象都修改了
console.log(obj)
console.log(anotherObj)
由此可見,JS中對象的賦值是一種淺拷貝。

熟悉了對象的本質以後,我們要逐步瞭解對象有哪些特性。

對象的屬性與方法

實際上,在學習一般高級語言的時候,應該先介紹類的屬性與方法(共性),才介紹實例化類產生的對象如何使用(特性)。但由於JS是以原型、對象為主的語言,類只能在ES6中以語法糖的形式存活,我們只能先從對象入手,反推類的性質。

對象其實就是一些屬性和一些方法的集合。而對象的屬性和方法要深究,其實也是非常複雜的問題(光看內置對象Object以及Object.prototype上有多少方法處理對象的屬性就知道不簡單):

屬性

描述符

每個屬性上有描述符號。所謂的描述符號,是一些鍵值對,它們描述了對於這個屬性是否能操作、是否能枚舉等等的所有特性。

描述符號只能是數據描述符和存取描述符兩個裡面的一個(在一般的聲明中,屬性預設含有的是數據描述符)。

首先,這兩種描述符公有的兩個屬性是:configurable(這個屬性的描述符是否能被修改、以及這個屬性是否能被delete運算符刪除)和enumerable(是否能被枚舉)。

然後是數據描述符,顧名思義,它定義了value(值)和writable(值是否能被賦值語句修改)。

最後是存取描述符,同樣的顧名思義,它定義了這個屬性的get(讀取時執行的函數)和set(修改時執行的函數)。

定義和修改屬性

我們可以通過Object.defineProperty來具體地配置一個屬性的描述符:

const foo = {}
// 1. 數據描述符
Object.defineProperty(foo, 'bar', {
    // 1.1. 固定了是數據描述符不能被修改
    configurable: false,
    // 1.2. 設置該屬性可以枚舉
    enumerable: true,
    // 1.3. 值為3
    value: 3,
    // 1.4. 無論怎麼賦值更新foo.bar,它的值仍然是3
    writable: false
})
// 2. 存取描述符
let baz = 3
Object.defineProperty(foo, 'baz', {
    // 2.1. 固定了是存取描述符不能被修改
    configurable: false,
    // 2.2. 設置該屬性可以枚舉
    enumerable: true,
    // 2.3. 使用foo.baz讀取時,會順帶輸出這句話
    get: function () {
        console.log('The getter is called.')
        return baz
    },
    // 2.4. 使用賦值語句為foo.baz賦值時,會順帶輸出這句話
    set: function (value) {
        console.log('The setter is called.')
        baz = value
    }
})
由此可見,定義屬性時可以根據自己的需求修改預設的描述符。

瞭解到這裡,我們不難聯想到,著名的前端框架Vue實現數據的雙向綁定,實際上就是利用了這個存取描述符。我們在編寫Vue代碼時,定義Vue對象中data屬性的值。Vue在編譯過程中,首先收集了這個值的所有依賴(也就是它在我們代碼中出現的各種地方),然後利用Object.defineProperty,把屬性的描述符改成存取描述,併在setter中修改所有的依賴,通知視圖更新。這樣就有了我們覺得非常神奇的數據雙向綁定。

遍歷屬性

對屬性的常用操作除了定義與修改,還有遍歷。最常用的遍歷方法是:

// 兩種方法,都只能遍歷enumerable的屬性
const obj = {
    foo: 1,
    bar: 2,
    baz: 3
}
// 1. Object.keys
let objAttrs = Object.keys(obj)
// 2. for...in...
let objAttrs = []
for (let key in obj) {
    objAttrs.push(key)
}
// 以上兩種遍歷的方法數量和順序均一致
// 如果不希望遍歷原型上的屬性,還可以使用Object.hasOwnProperty進行過濾
遍歷時需要考慮到屬性是否能被枚舉以及原型上的屬性是否需要被遍歷到。

方法

函數調用

函數有總共四種調用模式,這四種調用模式其實都是圍繞著this指向的不同而定的(下麵的全局在瀏覽器環境中表示window,在node環境中表示global):

  1. 普通函數調用 —— this指向全局
  2. 方法調用 —— this指向方法所定義的對象
  3. 構造器調用 ——(使用new關鍵字時)this指向當前函數對象(函數本身就是對象)
  4. (call、apply和bind)調用 —— this指向(call、apply和bind)函數的第一個參數
方法是什麼

方法就是定義在類或者對象上,用來處理對象有關數據的函數。簡而言之,方法就是函數的子集。方法特別於其他函數的點在於,它的this是指向當前對象的。

從方法到this

由此可見,我們通過不同的方式調用函數,最終為的還是根據自己的需求定義this的指向。我們試著來區分幾個例子,從而最終總結出JS中this的指向情況:

一般情況
// 定義一個對象,裡面有一個輸出對象自身的方法
const obj = {
  foo: function () {
    return this
  }
}
// 直接執行obj.foo方法,正常得到obj對象
console.log(obj.foo())
// 用一個外部變數接收obj.foo方法
const fakeFoo = obj.foo
// 執行這個接收回來的方法,獲得this為全局對象
console.log(fakeFoo())
上述例子說明,一般情況下,this指向的是函數被調用時所在的上下文環境。
(所謂函數調用時的上下文環境,實際上也等同於JS中的詞法作用域(lexical scope),即函數作用域)
內部函數

下麵再來看看內部函數的this指向:

// 定義一個對象,裡面有一個方法,方法裡面有一個返回this的內部函數
// 以此測試內部函數中this指向
const obj = {
    foo: function () {
        return function () {
            return this
        }
    }
}
// 執行這個內部的函數,發現this指向的是全局對象
console.log(obj.foo()())
上述例子說明,內部函數中,this沒有指向當前對象,而是指向的是全局。
箭頭函數

當然,ES6中箭頭函數的出現修複了這些問題,內部函數的this也能正確指向當前對象了:

// 僅僅把上述對象的內部函數換為箭頭函數
const obj = {
    foo: function () {
        return () => {
            return this
        }
    }
}
// 正確得到this為當前對象
console.log(obj.foo()())
上述例子說明,箭頭函數把this綁定回了詞法作用域。

但是,由於JS的詞法作用域為函數作用域,以下的寫法又會發生錯誤:

const obj = {
    foo: () => {
        return this
    }
}
// 得到的this為全局對象
console.log(obj.foo())
上述例子說明瞭,由於JS詞法作用域為函數作用域,箭頭函數沒有外部函數包著,因此是全局作用域。

但是,箭頭函數強制將this綁定到函數執行的上下文環境。這導致了bind、call與apply的失效。

// 定義一個對象,裡面有一個方法返回當前對象的foo屬性
// 並將這個方法應用到foo為2的新對象上
const obj = {
  foo: 1,
  bar: function () {
    const foo = this.foo
    const baz = function () {
        return foo
    }
    return baz.call({ foo: 2 })
  }
}
// 得到新對象的值為2
console.log(obj.bar())
正常情況下,call方法正常地將這個方法應用到另一個對象上。
// 僅將內部返回foo的函數改為箭頭函數
const obj = {
  foo: 1,
  bar: function () {
    const foo = this.foo
    const baz = () => {
        return foo
    }
    return baz.call({ foo: 2 })
  }
}
// 得到的還是舊的1,說明call方法並沒有成功將this綁定到新對象上
console.log(obj.bar())
而箭頭函數的this則被緊鎖在了舊對象上。

總結:

  • JS的6種基本數據類型:string、number、boolean、null、undefined、Symbol
  • JS中,除了6種基本數據類型以外,其他變數都是對象,我們通過操作指針對這些對象進行處理
  • 對象的屬性有兩種描述符的其中一種:數據描述符(預設)和存取描述符
  • 對象的方法中,this預設指向這個對象,而方法的內部函數this預設指向全局

拓展:

  • 通過Object.defineProperty可以定義和修改某個屬性的描述符
  • 普通函數中的this預設指向詞法作用域,使用new定義對象、call、apply、bind等內建方法,可以修改this的指向
  • 箭頭函數將this鎖在了詞法作用域,沒辦法使用call、apply、bind進行修改

您的分享是我們最大的動力!

更多相關文章
  • 簡單介紹了線性佈局和相對佈局,同時分析了xml文件三個固定開頭xmlns:namespace-prefix="namespaceURI" ...
  • 在無序列表ul>li中,無線列表的標誌是出現在各列表前面的圓點。在有序列表ol>li中,前面預設帶有數字,如何修改列表前面的項目符號,只需要通過list-style調整就好,常見的符號有(/*內容註釋部分*/)list-style-type:circle;/*空心圓*/list-style:none... ...
  • 常見的color, font-family, background 等css屬性都能夠設置鏈接的樣式,a鏈接的特殊性在於能夠根據它們所處的狀態來設置它們的樣式。a標簽與人交互的4個狀態屬於偽類狀態切換,常見的鏈接四種狀態為:a:link - 普通的、未被訪問的鏈接a:visited - 用戶已訪問的... ...
  • CSS文本屬性可定義文本的外觀,這是毫無疑問的,其次css可以通過以下屬性改變文字的排版,比方說letter-spacing實現字元間距text-indent: 2em;完成首行縮進2字元word-spacing改變了文字的間距,direction改變文本從左至右的閱讀順序,white-space處... ...
  • 對象的遍歷 對象可以當做數組處理,使用for in var person={}; person.name="cyy"; person.age=25; person.infos=function(){ alert(this.name+" "+this.age); } for(var i in pers ...
  • 前次用 electron-packager 打包成功,這次改用 electron-builder 打包,然後根據項目中實際需要進行選擇使用。 第一步:全局安裝 electron-builder,便於系統通用 npm install -g electron-builder 或 cnpm install ...
  • CSS 字體屬性定義文本的字體系列、大小、加粗、風格(如斜體)和變形(如小型大寫字母)font-family控制字體,由於各個電腦系統安裝的字體不盡相同,但是基本裝有黑體、宋體與微軟雅黑這三款字體,通常這樣寫font-family:"黑體", "宋體","Microsoft YaHei" font-... ...
  • jQuery初學者筆記 一 Mirror王宇陽 by jQuery語法 jQuery語法是通過選取HTML元素,並對選取的元素進行操作 基礎語法: 所有jQuery語句用“$”符號開始 jQuery函數位於一個document ready函數中,我們需要在js中載入該函數文檔 選擇器語法: jQue ...
一周排行
  • 枚舉是 C 中最有意思的一部分,大部分開發人員只瞭解其中的一小部分,甚至網上絕大多數的教程也只講解了枚舉的一部分。那麼,我將通過這篇文章向大傢具體講解一下枚舉的知識。我將從大家都瞭解的部分開始講解,然後再講解大家所不知道的或者瞭解很少的部分。 零、基礎知識 枚舉是由開發人員聲明的一種 值類型 ,它在 ...
  • 一. elasticsearch on windows 1.下載地址: https://www.elastic.co/cn/downloads/elasticsearch 如果瀏覽器下載文件慢,建議使用迅雷下載,速度很快。下載版本為7.5.2 2. 修改配置文件 下載後解壓,找到config\jvm ...
  • 最近因為” 新冠” 疫情在家辦公,學習了 ASP.NET Core MVC 網站的一些知識,記錄如下。 ...
  • Regex.Replace("<!--(.|[\r\n])*?-->",string.Empty) ...
  • 本筆記摘抄自:https://www.cnblogs.com/PatrickLiu/p/7743118.html,記錄一下學習過程以備後續查用。 一、引言 今天我們要講結構型設計模式的第四個模式--組合模式。當我們談到這個模式的時候,有一個物件和這個模式很像,那就是“俄羅斯套娃”。“俄羅斯套娃”是 ...
  • 一、前言 Entity Framework(後面簡稱EF)作為微軟家的ORM,自然而然從.NET Framework延續到了.NET Core。 二、程式包管理器控制台 為了能夠在控制臺中使用命令行來操作EF,需要先安裝Microsoft.EntityFrameworkCore.Tools。 安裝 ...
  • 項目gitHub地址 點我跳轉 今天給大家帶來一個C#裡面的時間工具類,具體的直接看下麵代碼 1 using System; 2 3 namespace ToolBox.DateTimeTool 4 { 5 public static class DateTimeExtend 6 { 7 /// < ...
  • 《C# 6.0 本質論》 [作者] (美) Mark Michaelis (美) Eric Lippert[譯者] (中) 周靖 龐燕[出版] 人民郵電出版社[版次] 2017年02月 第5版[印次] 2017年02月 第1次 印刷[定價] 108.00元 【前言】 成功學習 C# 的關鍵在於,要盡 ...
  • 本筆記摘抄自:https://www.cnblogs.com/PatrickLiu/p/7772184.html,記錄一下學習過程以備後續查用。 一、引言 今天我們要講結構型設計模式的第五個模式--外觀模式。先從名字上來理解一下外觀模式,當看到“外觀”這個詞時,很容易想到“外表”這個詞語,兩者有著 ...
  • 在 C 中存在一個名叫靜態類型檢查的機制,這個機制可以讓編譯器幫助我們把類型不服的用法找出來,從而使得應用程式在運行期間加少一些類型檢查的操作。但是有時候我們還是需要進行運行期類型檢查,比如我們在設計框架時將方法的參數類型定義為 object ,那麼這時我們就有很大的可能需要將 object 類型的 ...
x