說明
本文為作者學習記錄相關筆記及理解,如有不妥之處,請各位讀者積極指出,
雖然標題是深入理解,但可能存在許多不夠深入的地方,請各位小伙伴不吝賜教
一切都是物件
一切參考型別都是物件,物件是屬性的集合
值型別就不是物件
函式和物件的關系
物件都是通過函式創建的
物件是若干屬性的集合,一切參考型別都是物件
var obj = {name: 'zs', age: 20};
//等價于
var obj = new Object();
obj.name = 'zs';
obj.age = 20;
每個函式都有一個屬性prototype,其屬性值是一個物件,默認只有一個叫constructor的屬性,指向這個函式本身

每個物件都有一個隱藏的__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

隱式原型__proto__
每個物件都有一個隱式原型__proto__,是一個隱藏屬性,指向創建該物件的函式的prototype
var obj = {
name: "張三",
age: 20
};

可以看出,我們平時用定義的普通物件,就是用Object這個建構式創建的.
既然Object是一個函式,那么它也就有prototype屬性了

那么Object又是被誰創建出來的呢?
雖然Object是一個函式,但是函式也是物件,那么就有__proto__屬性,我們通過這個隱式原型就可以知道它是誰創建出來的了
??我們直接在控制臺輸入Object.__proto__是看不到想要的結果的
只能得到下圖,說明真的是由一個函式創建的

var obj = {name: "張三"};
console.log(obj.__proto__);
我們先獲取一個普通物件的隱式原型,找到Object函式,然后查看Object物件的隱式原型,即找到了創建了Object的函式,也就是Function函式

函式的原型物件也是一個物件,也擁有自己的原型物件
函式是Object的實體,因此函式的原型物件為Object的原型物件
function Person(name) {
this.name = name;
}
console.log(Person.prototype.__proto__ === Object.prototype)
//true
特例
Object.prototype的__proto__指向null

因此obj.__proto__和Object.prototype的屬性相等
通俗來說就是實體物件的原型物件等于其建構式的原型物件.
小結
-
每個函式的原型物件(
Person.prototype)都擁有constructor屬性,指向該函式本身 -
使用建構式(
new Person())可以創建物件,創建的物件稱為實體物件(zs); -
實體物件通過將
__proto__屬性指向建構式的原型物件(Person.prototype),實作了該原型物件的繼承,
JS資料型別
- 原始資料型別 存盤在堆疊上
booleannumberstringnullundefinedsymbol(es6)bigint(es10)
bigint它提供了一種方法來表示大于 253 - 1 的整數
- 物件型別 存盤在堆上,參考地址還是存在堆疊上
ObejectFunction
型別判斷
- 值型別用
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來進行判斷

判斷規則
typeof在判斷參考型別時,只會回傳Object和Function
而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

原型鏈
__proto__和prototype的關系
- 每個物件都有
__proto__屬性來標識自己所繼承的原型物件,但只有函式才有prototype屬性 - 每個函式都有一個
prototype屬性,該屬性為該函式的原型物件 - 通過將實體物件的
__proto__屬性賦值給其建構式的原型物件prototype,JavaScript可以使用建構式來創建物件的方式,實作繼承
一個物件可通過__proto__訪問原型物件上的屬性和方法,而該原型同樣也可通過__proto__訪問它的原型物件,這樣我們就在實體和原型之間構造了一條原型鏈

執行背景關系
在執行JavaScript代碼之前,需要一系列的準備作業,其中比較重要的就是編譯階段,而編譯階段主要負責執行背景關系的創建
執行背景關系也稱為詞法環境
JavaScript中有三種執行背景關系
- 全域執行背景關系
- 函式執行背景關系
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);

可以看到當前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標識

可以簡單理解為如果函式的回傳值是一個函式,并且回傳的函式中用到了外層函式的變數,那么外層函式就是一個閉包
示例:如果沒有使用變數,也就不存在閉包
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__===null,null沒有原型,并作為這個原型鏈中的最后一個環節; -
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
標籤:其他
