主頁 > 前端設計 > 深入理解JavaScript原型與閉包

深入理解JavaScript原型與閉包

2021-08-13 07:47:50 前端設計

說明

本文為作者學習記錄相關筆記及理解,如有不妥之處,請各位讀者積極指出,
雖然標題是深入理解,但可能存在許多不夠深入的地方,請各位小伙伴不吝賜教

一切都是物件

一切參考型別都是物件,物件是屬性的集合

值型別就不是物件

函式和物件的關系

物件都是通過函式創建的

物件是若干屬性的集合,一切參考型別都是物件

var obj = {name: 'zs', age: 20};
//等價于
var obj = new Object();
obj.name = 'zs';
obj.age = 20;

每個函式都有一個屬性prototype,其屬性值是一個物件,默認只有一個叫constructor的屬性,指向這個函式本身

image-20210802164017710

每個物件都有一個隱藏的__proto__屬性,指向創建這個物件的函式的prototype

Object.prototype.__proto__ ===null

函式也是物件,也有__proto__ Object.__proto__===Function.prototype

Function也是一個函式,是一種物件,有__proto__ 屬性,它一定是被Function創建,所以Function是被自身創建,它的__proto__指向自身的prototype

Function.prototype.__proto__===Object.prototype

Function.prototype所指向的物件也是被Object創建的物件

原型物件和物件的關系

JavaScript 中,物件由一組或多組的屬性和值組成:

{
  key1: value1,
  key2: value2,
  key3: value3,
}

不管是物件,還是函式和陣列,它們都是Object的實體,也就是說在 JavaScript 中,除了原始型別以外,其余都是物件

函式和物件的關系

函式是一種特殊的物件

物件都是通過函式創建的

//var obj = { a: 10, b: 20 };
//var arr = [5, 'x', true];

var obj = new Object();
obj.a = 10;
obj.b = 20;

var arr = new Array();
arr[0] = 5;
arr[1] = 'x';
arr[2] = true;

原型prototype

默認情況下,每個函式都有一個默認的屬性prototype,是一個物件,稱為原型物件,默認只有一個constructor屬性,指向這個函式本身

function Person(name) {
  this.name = name;
}
console.log(Person.prototype.constructor === Person);
//true

image-20210809195209786

隱式原型__proto__

每個物件都有一個隱式原型__proto__,是一個隱藏屬性,指向創建該物件的函式的prototype

var obj = {
    name: "張三",
    age: 20
};

image-20210809195620209

可以看出,我們平時用定義的普通物件,就是用Object這個建構式創建的.

既然Object是一個函式,那么它也就有prototype屬性了

image-20210809195901516

那么Object又是被誰創建出來的呢?

雖然Object是一個函式,但是函式也是物件,那么就有__proto__屬性,我們通過這個隱式原型就可以知道它是誰創建出來的了

??我們直接在控制臺輸入Object.__proto__是看不到想要的結果的

只能得到下圖,說明真的是由一個函式創建的

image-20210809200753181

var obj = {name: "張三"};
console.log(obj.__proto__);

我們先獲取一個普通物件的隱式原型,找到Object函式,然后查看Object物件的隱式原型,即找到了創建了Object的函式,也就是Function函式

image-20210809201655521

函式的原型物件也是一個物件,也擁有自己的原型物件

函式是Object的實體,因此函式的原型物件為Object的原型物件

function Person(name) {
  this.name = name;
}
console.log(Person.prototype.__proto__ === Object.prototype)
//true

特例

Object.prototype__proto__指向null

A7BFCCAB-78BE-4E84-BBA2-E7D25170115D

因此obj.__proto__Object.prototype的屬性相等

通俗來說就是實體物件的原型物件等于其建構式的原型物件.

小結
  • 每個函式的原型物件(Person.prototype)都擁有constructor屬性,指向該函式本身

  • 使用建構式(new Person())可以創建物件,創建的物件稱為實體物件(zs);

  • 實體物件通過將__proto__屬性指向建構式的原型物件(Person.prototype),實作了該原型物件的繼承,

JS資料型別

  • 原始資料型別 存盤在堆疊上
    • boolean
    • number
    • string
    • null
    • undefined
    • symbol(es6)
    • bigint(es10)

bigint它提供了一種方法來表示大于 253 - 1 的整數

  • 物件型別 存盤在堆上,參考地址還是存在堆疊上
    • Obeject
    • Function

型別判斷

  • 值型別用typeof
  • 參考型別用instansof
function show(x) {

  console.log(typeof x);    // undefined
  console.log(typeof 10);   // number
  console.log(typeof 'abc'); // string
  console.log(typeof true);  // boolean

  console.log(typeof function () {});  //function

  console.log(typeof [1, 'a', true]);  //object
  console.log(typeof { a: 10, b: 20 });  //object
  console.log(typeof null);  //object
  console.log(typeof new Number(10));  //object
}
show();

instanceof

型別判斷

  • 基本資料型別用typeof判斷即可
  • 參考型別用instansof來進行判斷

image-20210412185342069

判斷規則

typeof在判斷參考型別時,只會回傳ObjectFunction

instanceof運算子卻可以判斷參考型別,這是什么原理呢?

請看如下規則↓

A instanceof B

第一個引數是一個物件A,第二個物件是一個函式B

規則:沿著A的__proto__這條線來查找,同時沿著B的prototype這條線來查找,當兩條線查找到同一個參考時,說明為同一物件,回傳true,如果到終點還沒找到,回傳false

instanceof代表一種繼承關系,或者原型鏈的結構

JavaScript繼承通過原型鏈體現:訪問一個物件的屬性時,先在物件自身的屬性中查找,如果沒有找到再沿著__proto__查找其建構式上是否有該屬性.

hasOwnproperty()判斷是否是自身的屬性,從Object.prototype中來

所有物件的原型鏈都會找到Object.prototype

繼承

在訪問一個物件的屬性和方法時,先在其自身的屬性和方法中查找,如果沒有找到,再到其__proto__中查找,依次類推,直到null

我們都知道toString()這個方法,所有實體化的物件基本都有這個方法,我們創建時也沒有定義,那么它是哪里來的呢?答案是繼承來的

例如

var obj = {name: "張三"};

obj.toString === Object.prototype.toString;
//true

image-20210809204514929

原型鏈

__proto__prototype的關系

  • 每個物件都有__proto__屬性來標識自己所繼承的原型物件,但只有函式才有prototype屬性
  • 每個函式都有一個prototype屬性,該屬性為該函式的原型物件
  • 通過將實體物件的__proto__屬性賦值給其建構式的原型物件prototypeJavaScript可以使用建構式來創建物件的方式,實作繼承

一個物件可通過__proto__訪問原型物件上的屬性和方法,而該原型同樣也可通過__proto__訪問它的原型物件,這樣我們就在實體和原型之間構造了一條原型鏈

img

執行背景關系

在執行JavaScript代碼之前,需要一系列的準備作業,其中比較重要的就是編譯階段,而編譯階段主要負責執行背景關系的創建

執行背景關系也稱為詞法環境

JavaScript中有三種執行背景關系

  1. 全域執行背景關系
  2. 函式執行背景關系
  3. eval

注意:eval很少用,且極不推薦使用

在創建背景關系的程序中,主要包括三步

  • 建立作用域鏈(Scope Chain);
  • 創建變數物件(Variable Object,簡稱 VO);
  • 確定 this的指向,

本文主要介紹創建變數物件

變數物件和執行背景關系直接關聯,在這個背景關系中的所有變數和函式都是在這個物件之上創建的,

例如:全域環境中的window,我們使用var關鍵字創建的變數都是window物件的一個屬性,這一點可自行驗證

創建變數物件的程序

  • 變數宣告:分配記憶體,初始化為undefined
  • 函式宣告:記憶體中創建函式物件,并初始化為函式物件

如果是函式執行背景關系

  • 初始化引數
  • 創建arguments物件
  • 確定自由變數的取值作用域

自由變數:在函式中使用但未在函式中定義的變數

執行背景關系堆疊

第一次加載JavaScript代碼時,會創建一個全域執行背景關系,每次呼叫函式又會產生函式執行背景關系并將其設定為當前活動背景關系,函式執行完成會退出,銷毀自身的執行背景關系,又回到全域背景關系

這樣的程序就是執行背景關系堆疊的壓堆疊出堆疊程序

作用域

作用域可以理解為當前執行背景關系,注意是當前

作用域本身沒有變數和方法的值,只有在對應的執行背景關系中才有

也就是說處于不同執行背景關系的變數會有不同的取值

例如

var age = 10;
function test () {
    var age = 100;
    console.log(age);
};
test();//100

以上代碼輸出100,因為在test函式的執行背景關系中,變數age的值為100,而變數age的作用域是在函式呼叫時就確定了,所以雖然我們是在全域作用域下呼叫的函式,但age的取值還是會回到定義它的那個作用域去尋找

作用域也是有上下級關系的,確定了函式或變數是在哪個作用域下創建的

例如:

function test () {
    var a = 100;
};
console.dir(test);

image-20210809212019605

可以看到當前test函式處于Global作用域

總結

作用域只是一個空間,里面沒有變數,需要通過對應的當前執行背景關系來獲取變數,注意是當前執行背景關系,因為在創建階段只是宣告,在執行階段才會進行賦值

同一個作用域下,不同的呼叫會產生不同的當前執行背景關系,繼而產生不同的變數值,也就是說變數值是在執行程序中確定下來的

要查找一個作用域下的某個變數值,就需要找到這個作用域對應的當前執行背景關系

閉包

閉包簡單來說就是在一個函式內部使用外部函式的值

一般有兩種情況

  • 函式作為回傳值傳遞
  • 函式作為引數傳遞

這兩種都稱為高階函式

函式作為回傳值傳遞

示例:

function fn() {
	var age = 10;    
	return function bar(num) {
		console.log("bar函式",num + age);
    }
};
var f1 = fn();
f1(15);//bar函式 25

bar函式作為fn函式的回傳值,賦值給f1變數,執行f1(15)時,用到了fn作用域下的age變數

通過除錯可以看到,fn函式就是一個閉包,Closure標識

image-20210809214433510

可以簡單理解為如果函式的回傳值是一個函式,并且回傳的函式中用到了外層函式的變數,那么外層函式就是一個閉包

示例:如果沒有使用變數,也就不存在閉包

function fn() {
	var age = 10;    
	return function bar(num) {
		console.log("bar函式",num);
    }
};
var f1 = fn();
f1(15);//bar函式 15

函式作為引數傳遞

var age = 10;
var fn = function(num) {
    console.log(num + age)
};
(function f(f) {
    var age = 100;
    f(15);
})(fn);
//25

fn函式作為一個引數傳進另外一個函式,本例中是一個立即執行函式,fn函式賦值給f引數,執行f(15)時,age變數的取值是10,

因為上文說過,要去創建函式的作用域中取值,函式fn在全域作用域取值,全域作用域中age的值為10

順便說說,以下內容為擴展

通過原型鏈訪問物件的方法和屬性

JavaScript 中,是通過遍歷原型鏈的方式,來訪問物件的方法和屬性,

  • JavaScript 試圖訪問一個物件的屬性時,會基于原型鏈進行查找,查找的程序是這樣的:

  • 首先會優先在該物件上搜尋,如果找不到,還會依次層層向上搜索該物件的原型物件、該物件的原型物件的原型物件等(套娃告警);

  • JavaScript 中的所有物件都來自Object,Object.prototype.__proto__ === nullnull沒有原型,并作為這個原型鏈中的最后一個環節;

  • JavaScript 會遍歷訪問物件的整個原型鏈,如果最終依然找不到,此時會認為該物件的屬性值為undefined

由于通過原型鏈進行屬性的查找,需要層層遍歷各個原型物件,此時可能會帶來性能問題:

  • 當試圖訪問不存在的屬性時,會遍歷整個原型鏈;
  • 在原型鏈上查找屬性比較耗時,對性能有副作用,這在性能要求苛刻的情況下很重要,

因此,我們在設計物件的時候,需要注意代碼中原型鏈的長度,當原型鏈過長時,可以選擇進行分解,來避免可能帶來的性能問題,

使用 prototype 和 proto 實作繼承

JavaScript物件的屬性值可以為任意型別,因此,屬性的值同樣可以為另外一個物件,這意味著 JavaScript 可以這么做:通過將物件 A 的__proto__屬性賦值為物件 B,即A.__proto__ = B,此時使用A.__proto__便可以訪問 B 的屬性和方法,

var zs = new Person("張三");

JavaScript引擎實際上執行了以下代碼

var zs = {};
zs.__proto__ = Person.prototype;
Person.call(zs, '張三');

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/293374.html

標籤:其他

上一篇:JavaScript為什么是單執行緒-JS異步與回呼詳解

下一篇:JavaScript DOM超細筆記(貳)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more