1. 程式人生 > >JS(二)基本知識

JS(二)基本知識

一 預解釋

1、預解釋(變數提聲):在當前的作用域中,JS程式碼執行之前,瀏覽器首先會預設的把所有帶var和function的進行提前的宣告或者定義。
2、var和function在預解釋的時候操作不同:

  • var:在預解釋的時候只是提前的宣告
  • function:在預解釋的時候提前的宣告和定義都完成了。

3、預解釋只發生在當前的作用域下,例如,開始只對window下的進行預解釋,只有函式執行的時候才會對函式中的程式碼進行預解釋。
4、預解釋的時候不管條件是否成立,只要是帶var的都要進行提前的宣告。
5、預解釋的時候只預解釋等號左邊的,右邊的是值,不參與預解釋。
6、函式體中return下面的程式碼雖然不在執行了,但是需要進行預解釋,return後面跟的是返回值,所以不進行預解釋。
7、在預解釋的時候,如果名字已經宣告過了,不需要重新宣告,但是需要重新的賦值。在JS中,如果變數的名字和函式的名字重複了,也算衝突。

1、如何區分私有變數和全域性變數?
(1)在全域性作用域下宣告(預解釋的時候)的變數是全域性變數。
(2)在“私有作用域中宣告的變數”“函式的形參”都是私有變數。

2、當函式執行的時候(直接目的:讓函式體中的程式碼執行),首先會形成一個新的私有作用域,然後按照如下步驟執行:
(1)如果有形參,先給形參賦值,
(2)進行私有作用域中的預解釋,
(3)私有作用域中的程式碼從上到下執行
......
函式形成的一個新的私有的作用域保護了裡面的私有變數不受外界的干擾(外面改變不了私有的,私有的也修改不了外面的),把這種機制稱為閉包。

三作用域鏈

在私有作用域中,我們程式碼執行的時候遇到了一個變數,首先我們需要確定他是否為私有的變數,如果是私有的變數,那麼和外面的任何東西沒有任何的關係,如果不是私有的,則往當前作用域的上級作用域進行查詢,如果上級作用域也沒有則繼續查詢,一直找到window為止...(作用域鏈)。

在全域性作用域中,帶var和不帶var的關係?
區別:帶var的可以進行預解釋,所以在賦值的前面執行不會報錯;不帶var的是不能進行預解釋的,在前面執行會報錯。
關係:不帶var宣告,num=12; ---->相當於給window增加了一個叫做num2的屬性名,屬性值是12 。帶var宣告,var num=12;----->首先相當於給全域性作用域增加了一個全域性變數num,但是不僅如此,他也相當於給window增加了一個屬性名num2,屬性值是12。

如何查詢當前作用域的上一級作用域?
看當前函式是在哪個作用域下定義的,那麼他的上級作用域就是誰,和函式在哪執行的沒有任何關係。

四 JS記憶體

1、記憶體分類
棧記憶體:用來提供一個供JS程式碼執行的環境。--->作用域(全域性作用域/私有作用域)
堆記憶體:用來儲存引用資料型別的值,物件儲存的是屬性名和屬性值,函式儲存的是程式碼字串。
2、記憶體釋放
(1)堆記憶體記憶體釋放:物件資料型別或者函式資料型別在定義的時候首先都會開闢一個堆記憶體,堆記憶體有一個引用的地址,如果外面有變數等知道了這個地址,我們就說這個記憶體被佔用了,就不能銷燬了。
我們想要讓堆記憶體釋放/銷燬,只需要把所有引用他的變數賦值為null即可,如果當前的堆記憶體沒有任何東西被佔用了,那麼瀏覽器會在空閒的時候把他銷燬...
(2)棧記憶體
全域性作用域:只有當頁面關閉的時候全域性作用域才會銷燬。
私有作用域(只有函式執行才會產生私有作用域):一般情況下,函式執行會形成一個新的私有作用域,當私有作用域中的程式碼執行完成後,我們當前作用域都會主動的進行釋放和銷燬。
但是還是存在特殊的情況的:
當前私有作用域中的部分記憶體被作用域以為的東西佔用了,那麼當前的作用域就不能銷燬了。
a)函式執行返回了一個引用資料型別的值,並且在函式的外面被一個其他的東西給接收了,這種情況下一般形成的私有作用域都不會被銷燬。
b)在一個私有的作用域中給DOM元素的事件繫結方法,一般情況下我們的私有作用域都不銷燬。
c)下述情況屬於不立即銷燬--->fn返回的函式沒有被其他的東西佔用,但是還需要執行一次,所以暫時不銷燬,當返回的值執行完成後,瀏覽器會在空閒的時候把他銷燬了。---->“不立即銷燬”。

五 this關鍵字

  • JS中的this代表的是當前行為執行的主體,JS中的context代表的是當前行為執行的環境(區域)。
  • this是誰和函式在哪定義的和在哪執行的都沒有任何的關係。

如何區分this?
1、函式執行,首先看函式名前面是否有“.”,有的話,“.”前面是誰,this就是誰,沒有的話this就是window。
2、自執行函式中的this永遠是window。
3、給元素的某一個事件繫結方法,當事件觸發的時候,執行對應的方法,方法中的this就是當前的元素。
4、在建構函式模式中,類中(函式體中)出現的this.xxx = xxx中的this是當前類的一個例項。

六 類的繼承、封裝、多型

1、函式的封裝:把實現同一件事情的相同的程式碼放到一個函式中,以後如果在想實現這個功能,不需要重新編寫這些程式碼了,只需要執行當前的函式即可。“低耦合高內聚”。
2、繼承:子類繼承父類中的屬性和方法。
3、多型:當前方法的多種形態。
4、後臺語言中,多型包含過載和重寫。
5、過載:兩個方法名相同,兩個方法的引數不一樣,執行的時候呼叫不同的方法來執行。
6、JS中不存在過載,方法名如果一樣的話,後面的會把前面的覆蓋掉,最後只保留一個。
7、JS中有一個操作類似過載但是不是過載,我們可以根據傳遞的引數不同,實現不同的功能。
8、重寫:子類重寫父類的方法。

七 建構函式模式

建構函式模式的目的就是為了建立一個自定義類,並且建立這個類的例項。
建構函式模式和工廠模式的區別:

  • 1、執行的時候:
    普通函式執行-->createPerson()
    建構函式模式-->new CreatePerson() 通過new執行後,我們的CreatePerson就是一個類了。(一般,類的首字母大寫。)
    而函式執行的返回值就是CreatePerson這個類的一個例項。
    JS中所有的類都是函式資料型別的,他通過new執行變成了一個類,但是他本身也是一個普通的函式。JS中所有的例項都是物件資料型別的。

  • 2、在函式程式碼執行的時候:
    相同:都是形成一個私有作用域,然後經歷了形參賦值、預解釋、程式碼從上到下執行(類和普通函式一樣,他也有普通的一面)
    不同:在程式碼執行之前,不用自己手動的建立物件了,瀏覽器會預設的建立一個物件資料型別的值(這個物件其實就是我們當前類的一個例項)。
    接下來程式碼從上到下執行,以當前的例項為執行的主體(this代表的就是當前的例項),然後分別的把屬性名和屬性值賦值給當前的例項,最後瀏覽器會預設的把建立的例項返回。

  • 1、hasOwnproperty:用來檢測某一個屬性是否為這個物件的“私有屬性”,這個方法只能檢測私有的屬性。

console.log(f1.hasOwnproperty("getX"));
  • 2、檢測某一個屬性是否為該物件的“公有屬性” --->>hasPubProperty
function hasPubProperty(obj,attr) {
    //首先保證是他的一個屬性並且還不是私有的屬性,那麼只能是公有的屬性了
    return (attr in obj) && !obj.hasOwnProperty(attr) ;
}
console.log(hasPubProperty(f1,"getX");
  • 3、instanceOf:檢測某一個例項是否屬於這個類
  • 4、in:用來檢測某一個屬性是否屬於這個物件(attr in object),不管是私有的屬性還是公有的屬性,只要存在,用in來檢測都是true。

八 原型鏈模式

建構函式模式中擁有了類和例項的概念,並且例項和例項之間是相互獨立分開的,解決了例項識別的問題。
基於建構函式模式的原型模式解決了方法或者屬性公有的問題,把例項之間相同的屬性和方法提取成公有的屬性和方法,想讓誰公有就把他放在CreatePerson.prototype上即可。

  • 1、每一個函式資料型別(普通函式、類)都有一個天生自帶的屬性:prototype(原型),並且這個屬性是一個物件資料型別的值。
  • 2、並且在prototype上瀏覽器天生給他加了一個屬性constructor(建構函式),屬性值是當前函式(類)本身。
  • 3、每一個物件資料型別(普通的物件、例項、prototype、陣列、正則、函式)也天生自帶一個屬性:_proto_ ,屬性值是當前例項所屬類的原型(prototype)。
function Fn() {
    this.x = 100;
}
Fn.prototype.getX = function () {
    console.log(this.x);
};
var f1 = new Fn();
var f2 = new Fn();

8.png
所有物件資料型別的例項都是object。
1、
1)f1 instanceOf Object --> true 因為f1通過_proto_ 可以向上級查詢,不管有多少級,最後總能找到object。
2)object是JS中所有物件資料型別的基類(最頂層的類)。
3)在object.prototype上沒有_proto_這個屬性。
2、原型鏈模式

f1.hasOwnProperty("x");       

3、hasOwnProperty是f1的一個屬性,但是我們發現在f1的私有屬性上並沒有這個方法,那如何處理呢?

  • 通過 “物件名.屬性名” 的方式獲取屬性值的時候,首先在物件的私有的屬性上進行查詢,如果私有中存在這個屬性,則獲取的是私有的屬性值。如果私有中沒有這個屬性,則通過_proto_找到所屬類的原型(類的原型上定義的屬性和方法都是當前例項的公有的屬性和方法),原型上存在的話,獲取的是公有的屬性值;如果原型上也沒有,則繼續通過原型上的_proto_繼續向上查詢,一直找到object.prototype為止...
    這種查詢的機制就是我們的“原型鏈模式”
    在IE瀏覽器中,原型模式也是同樣的原理,但是IE瀏覽器怕你通過_proto_把公有的修改,禁止我們使用_proto_ 。
    4、在原型模式中,this常用的有兩種情況:
    1)在類中this.xxx = xxx; this---->當前類的例項
    2)某一個方法中的this---> 看執行的時候 “.” 前面是誰this就是誰。
  • a)需要先確定this的指向(this是誰)。
  • b)把this替換成對應的程式碼。
  • c)按照原型鏈查詢的機制,一步步的查詢結果。

批量設定公有屬性
1、起一個別名:

function Fn() {
    this.x = 100;
}
var pro = Fn.prototype;   把原來原型指向的地址賦值給Pro,現在他們操作的是同一個記憶體空間
pro.getX = function () {

};
pro.getY = function () {

};
pro.getZ = function () {

};

9.png

2、重構原型物件的方式:自己新開闢一個堆記憶體,儲存我們共有的屬性和方法,瀏覽器原來給Fn.prototype開闢的那個替換掉。

  • (1)只有瀏覽器天生給Fn.prototype開闢的堆記憶體裡面才有constructor,而我們自己開闢的這個堆記憶體沒有這個屬性,這樣constructor指向就不再是Fn而是Object了。
    console.log(f.constructor) ->沒做處理之前Object
    為了和原來的保持一致,我們需要手動的增加constructor這個屬性。
  • (2)用這種方式給內建類增加公有的屬性。我們這種方式會把之前已經存在於原型上的屬性和方法給替換掉,所以我們用這種方法修改內建類的話,瀏覽器是給遮蔽掉的。但是我們可以一個個的修改內建的方法,當我們通過下述方式在陣列的原型上增加方法,如果方法名和原來的內建重複了,會把人家內建的修改掉。---->我們以後在內建類的原型上增加方法,命名都需要加特殊的字首。
function Fn() {
    this.x = 100;
}
Fn.prototype = {
    constructor:Fn,   為了和原來的保持一致,我們需要手動的增加constructor這個屬性。
    a: function () {
},
<span class="hljs-attr">b</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{

};

};

10.png

1、for in迴圈在遍歷的時候預設的話可以把自己私有的和在他所屬類原型上擴充套件的屬性和方法都可以遍歷到,但是一般情況下,我們遍歷一個物件只需要遍歷私有的即可,我們可以使用以下的判斷進行處理:

if(obj.propertyIsEnumerable(key))  {
    console.log(key);
}
或者
if(obj.hasOwnProperty(key))  {
    console.log(key);
}

2、Object.create():建立一個擁有指定原型和若干個指定屬性的物件。

var obj = { getX: function () { } };
function Fn()  {
};
Fn.prototype = obj;

原型鏈模式的常用繼承方式

1、原型繼承:是我們JS中最常用的一種繼承方式。

  • 子類B想要繼承父類A中的所有的屬性和方法(私有和公有),只需讓B.prototype= new A即可。
function A() {
    this.x = 100;
}
A.prototype.getX = function () {
    console.log(this.x);
};
function B() {
    this.y = 200;
}
B.prototype = new A;
  • 原型繼承的特點:他是把父類中私有的和公有的都繼承到了子類原型上(子類公有的)。
  • 核心:原型繼承並不是把父類中的屬性和方法克隆一份一模一樣的給B,而是讓B和A之間增加了原型鏈的連線,以後B的例項n想要A中的getx方法,需要一級級的向上查詢來使用。

2、call繼承:把父類私有的屬性和方法,克隆一份一模一樣的,作為子類私有的屬性。

function A() {
    this.x = 100;
}
A.prototype.getX = function () {
    console.log(this.x);
};
function B() {
    this.x = 200;
    A.call(this);
}
var n = new B;

3、冒充物件繼承:把父類私有的和公有的克隆一份一模一樣的,給子類私有的。

function A() {
    this.x = 100;
}
A.prototype.getX = function () {
    console.log(this.x);
};
function B() {
    var temp = new A;
    for (var key in temp) {
        this[key] = key[key];
    }
    temp=null;
}

4、混合模式繼承:原型繼承+call繼承
5、寄生組合式繼承

function A() {
    this.x = 100;
}
A.prototype.getX = function () {
    console.log(this.x);
};
function B() {
    A.call(this);
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
var n = new B;
console.dir(n);
//相容IE瀏覽器
fuction objecrCreate(o) {
    function fn() {
}
fn.prototype = o;
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> fn;

}

所有類的原型都是物件資料型別的,所有類本身都是函式資料型別的。

函式本身屬性

  • length:0 形參的個數
  • name:“Fn” 函式名
  • prototype:類的原型,在原型上定義的方法都是當前Fn這個類例項的公有方法。
  • _proto_:把函式當做一個普通的物件,指向function這個類的原型。

1、call方法
首先我們讓原型上的call方法執行,在執行call方法的時候,我們讓fn方法中的this變為第一個引數值obj,然後再把fn這個函式執行。

var obj = {name:"zzh"};
function fn() {
    console.log(this);
}
fn.call(obj);

2、apply方法
apply和call方法的作用是一模一樣的,都是用來改變方法的this關鍵字並且把方法執行,而且在嚴格模式下和非嚴格模式下對於第一個引數是null/undefined這種情況的規律都是一樣的。

fn.call(obj,100,200);
fn.apply(obj,[100,200]);
  • apply給fn傳遞引數時,是把要傳遞的引數值統一的放在一個數組中進行操作,但是也相當於一個個的給fn的形參賦值。
    3、bind方法
    IE6-8不相容。和call/apply類似都是用來改變this關鍵字的。
fn.bind(obj,1,2);
只是改變了fn中的this為obj,並且給fn傳遞了兩個引數值1,2,但是此時並沒有把fn這個函式執行。
var tempFn = fn.bind(obj,1,2);
tempFn();
執行bind會有一個返回值,這個返回值tempFn就是我們把fn的this改變後的結果
  • bind會預處理:事先把fn的this改變為我們想要的結果,並且把對應的引數值也準備好,以後要用到了,直接的執行即可。

嚴格模式下的this相對於非嚴格模式下的this主要區別在於:
對於JS程式碼中沒有寫執行主體的情況下,非嚴格模式下預設都是window執行的,所以this指向的就是window,但是在嚴格模式下,沒有寫就是沒有執行主體,this指向的是undefined。

十 try/catch

  • 程式碼執行報錯,如果用try/catch捕獲了異常資訊,不影響下面的程式碼繼續執行。
  • 如果try中的程式碼執行出錯了,會預設的去執行catch中的程式碼。
try {
    console.log(num);
} catch(e)  {
    形參必須要寫,一般起名為e
    console.log(e.massege);
    e.massege可以收集當前程式碼報錯的原因,把其進行統計
}
consolo.log("zzh");
try {
    console.log(num);
} finally  {
    一般不用:不管try中的程式碼是否報錯,都要執行finally中的程式碼
}

有時候既想捕獲到錯誤的資訊,又不想讓下面的程式碼執行,所以需要終止程式碼執行。

try {
    console.log(num);
} catch(e)  {
    throw new Error("當前網路繁忙,請稍後再試");
}

十一 JSON

11.png

JSON不是一個單獨的資料型別,他只是一個特殊的資料格式,他是物件資料型別的。

var obj = {name:"zzh",age:18};
var jsonObj = {"name":"zzh","age":18}; 
  • JSON格式的物件,相對於普通的格式來講,只是屬性名用雙引號(只能是雙引號)包起來了。
    1、在window瀏覽器物件中,提供了一個叫做json的屬性,它裡面提供了兩個方法。----->window.JSON
    (1)JSON.parse:把JSON格式的字串轉換為JSON格式的物件。
var str = ' {"name":"zzh","age":18} '; 
JSON.parse (str);
eval("("+str+")"); 一定要記住使用eval的話,不要忘記手動加一個小括號

(2)JSON.stringify:把JSON格式的物件轉換為JSON格式的字串
3、DOM迴流(重排、reflow):當頁面中的HTML結構發生改變(增加、刪除元素、位置發生改變......),瀏覽器都需要重新計算一遍新的DOM結構,重新對當前的頁面進行渲染。
重繪:某一個元素的部分樣式發生改變了(背景顏色...),瀏覽器只需要重新的渲染當前的元素即可。

十二

DOM對映的機制:頁面中的標籤和JS中獲取到的元素物件(元素集合)是緊緊繫結在一起的,頁面中的HTML結構改變了,JS中不需要重新獲取,集合裡面的內容也會跟著自動改變。