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

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

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進行修改

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

-Advertisement-
Play Games
更多相關文章
  • 簡單介紹了線性佈局和相對佈局,同時分析了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 ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...