ES5(下)

這是ES5的入門篇教程的筆記,網址:JavaScript教程,如下內容中黑體表示大標題,還有一些重點;斜體表示對於自身,還須要下功夫學習的內容。這裏面有一些本身的看法,因此如果發現問題,歡迎指出~html

實例對象與new命令
面向對象編程是目前主流的編程範式,它將真實世界各類複雜的關係,抽象爲一個個對象,而後由對象之間的分工與合做,完成對真實世界的模擬。
對象是單個事物的抽象。
對象是一個容器,封裝了屬性(property)和方法(method)。
屬性是對象的狀態,方法是對象的行爲。二者最主要的區別在於屬性屬於對象靜態的一面,方法屬於對象動態的一面。
構造函數
面向對象編程的第一步,就是要生成對象,一般須要一個模板,表示某一類實物的共同特徵,而後對象根據這個模板生成。
典型的面向對象編程語言(好比C++和JAVA),都有「類」(class)這個概念。所謂「類」就是對象的模板,對象就是「類」的實例。可是JavaScript語言的對象體系,不是基於「類」的,而是基於構造函數(constructor)和原型鏈(prototype)。
構造函數,爲了與普通函數區別,構造函數名字的第一個字母一般大寫。它有兩個特色:1)函數體內部使用了this關鍵字,表明了所要生成的對象實例;2)生成對象的時候,必須使用new命令。
new命令
new命令的做用,就是執行構造函數,返回一個實例對象。
new命令自己就能夠執行構造函數,因此後面的構造函數能夠帶括號,也能夠不帶括號。可是爲了表示這裏是函數調用,推薦使用括號。node

// 推薦的寫法
let v = new Vehicle();
// 不推薦的寫法
let v = new Vehicle(); // 這兩種寫法都是等價的

new命令的原理
使用new命令時,它後面的函數依次執行下面的步驟:編程

一、建立一個空對象,做爲將要返回的對象實例;
二、將這個空對象的原型,指向構造函數的prototype屬性;
三、將這個空對象賦值給函數內部的this關鍵字;
四、開始執行構造函數內部的代碼。

若是構造函數內部由return語句,並且return後面跟着一個對象,new命令會返回return語句指定的對象;不然,就會無論return語句,返回this對象。(也就是說,new後,只能返回對象,要麼是自身,要麼是一個新對象。)
構造函數與普通函數最主要的區別,是內部有沒有this關鍵字的函數。
對普通函數使用new命令,會返回一個空對象。小程序

function getMessage() {
    let a = 1;
    return 'this is a message';
}
let msg = new getMessage(); // {}
typeof msg // "object"

構造函數做爲模板,能夠生成實例對象,可是,有時拿不到構造函數,只能拿到一個現有的對象,經過Object.create()方法,能夠將這個現有的對象做爲模板,生成新的實例對象。數組

let person1 = {
    name: '張三',
    greeting: function() {
        console.log('Hi! I\'m ' + this.name + '.');
    }
}
let person2 = Obejct.create(person1); // person2繼承了person1的屬性和方法。
person2.name // 張三
person2.greeting() // Hi! I'm 張三.

this關鍵字
若是this所在的方法不在對象的第一層,這時this只是指向當前一層的對象,而不會繼承更上面一層。
因爲this的指向是不肯定的,因此切勿在函數中包含多層的this。瀏覽器

let a = {
    p: 'Hello',
    b: {
        m: function() {
            console.log(this.p);
        }
    }
};
a.b.m() // undefined a.b.m方法在a對象的第二層,該方法內部的this不是指向a,而是指向a.b。
let o = {
    f1: function () {
        console.log(this);
        let f2 = function () {
            console.log(this);
        }(); // 這裏是執行函數了,就變成了值
    }
}
o.f1()
// Object 第一層指向對象o
// Window 第二層指向全局對象

// 實際執行的以下
let temp = function () {
    console.log(this);
};
let o = {
    f1: function() {
        console.log(this);
        let f2 = temp();
    }
}

數組中的map和foreach方法,容許提供一個函數做爲參數,這個函數內部不該該使用this。由於二者回調的this,是指向window對象的。(內層的this不指向外部,而是指向頂層對象)解決這種方法能夠用中間變量,也能夠將this看成foreach方法的第二個參數,固定運行環境。
JavaScript提供了call、apply、bind三個方法,來切換/固定this的指向。
函數實例的call方法,能夠指定函數內部this的指向(即函數執行時所在的做用域),而後在所指定的做用域中,調用該函數。
call的第一個參數就是this所要指向的那個對象,後面餓參數則是函數調用時所需的函數。
apply方法的做用與call方法相似,也是改變this指向,而後再調用該函數。惟一的區別就是,它接收一個數組做爲函數執行時的參數。
若是兩個方法沒有參數,或者參數爲null或undefined,則等同於指向全局對象。服務器

let obj = {};
let f = function () {
    return this;
};
f() === window // true
f.call(obj) === obj // true

func.call(thisVlaue, arg1, arg2, ...)
func.apply(thisValue, [arg1, arg2, ...])
function f(x, y) {
    console.log(x + y);
}
f.call(null, 1, 1) //2
f.apply(null, [1, 1]) //2

// JS不提供找出數組最大元素的函數,結合apply方法和Math.max方法,就能夠返回數組的最大元素
let a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15

bind方法用於將函數體內的this綁定到某個對象,而後返回一個新函數。app

let d = new Date();
d.getTime() // 1561974996108
let print = d.getTime; // 賦值後,內部的this已經不指向Date對象的實例了
print() // Uncaught TypeError: this is not a Date object.

// 使用bind
let print = d.getTime.bind(d);
print() // 1561974996108

空元素(null)與undefined的差異在於,數組的forEach方法會跳過空元素,可是不會跳過undefined。異步

對象的繼承
大部分面向對象的編程語言,都是經過「類(class)」實現對象的繼承。傳統上,JavaSCript語言的繼承不經過class,而是經過「原型對象(prototype)」實現。
構造函數的缺點:同一個構造函數的多個實例之間,沒法共享屬性,從而形成對系統資源的浪費。async

function Cat(name, color) {
    this.name = name;
    this.color = color;
    this.meow = function () {
        console.log('喵喵');
    };
}
let cat1 = new Cat('大毛', '白色');
let cat2 = new Cat('二毛', '黑色');
cat1.meow === cat2.meow // false cat1和cat2是同一個構造函數的兩個實例,它們都具備meow方法。因爲meow方法是生成在每一個實例對象上面的,因此兩個實例就生成了兩次,沒有必要,也浪費了系統資源,須要共享,也就是JavaScript的原型對象(prototype)。

prototype屬性的做用
JavaScript繼承機制的涉及思想就是,原型對象的全部屬性和方法,都能被實例對象共享。也就是說,若是屬性和方法定義在原型上,那麼全部實例對象就能共享,不只節省了內存,還體現了實例對象之間的聯繫。(感受像是Java類中的公共屬性和公共方法同樣。。。)
JavaScript規定,每一個函數都有一個prototype屬性,指向一個對象。對於普通函數來講,該屬性基本無用,可是,對於構造函數來講,生成實例的時候,該屬性會自動成爲實例對象的原型。

function f() {}
typeof f.prototype // "object"

function Animal(name) {
    this.name = name;
}
Animal.prototype.color = 'white'; // 原型對象上添加一個color屬性,下面的實例對象都共享了該屬性。
let cat1 = new Animal('大毛');
let cat2 = new Animal('二毛');
cat1.color // 'white'
cat2.color // 'white'
// 原型對象的屬性不是實例對象自身的屬性。只要修改原型對象,變更就馬上會體如今全部實例對象上
Animal.prototype.color = 'yellow';
cat1.color // "yellow"
cat2.color // "yellow"
// 若是實例對象自身就有某個屬性或方法,它就不會再去原型對象尋找這個屬性或方法。
cat1.color = 'black';
cat1.color // 'black'
cat2.color // 'yellow'

綜上,原型對象的做用,就是定義全部實例對象共享的屬性和方法,而實例對象能夠視做從原型對象衍生出來的子對象。
原型鏈
JavaScript規定,全部對象都有本身的原型對象(prototype)。一方面,任何我一個對象,均可以充當其餘對象的原型;另外一方面,因爲原型對象也是對象,因此它也有本身的原型。所以,就會造成一個「原型鏈」(prototype chain):對象到原型,再到原型的原型……若是一層層地上溯,全部對象的原型最終均可以上溯到Object.prototype,即Object構造函數的prototype屬性。也就是說,全部對象都繼承了Object.prototype的屬性。這就是全部對象都有valueOf和toString方法的緣由,由於這是從Object.prototype繼承的。
其實,Object.prototype對象也有他的原型,Object.prototype的原型是null。null沒有任何屬性和方法,也米有本身的原型。所以,原型鏈的盡頭就是null。

Object.getPrototypeOf(Object.prototype) // null

若是對象自身和它的原型,都定義了一個同名屬性,那麼優先讀取對象自身的屬性,這叫作「覆蓋」(overriding)。舉例來講,若是讓構造函數的prototype屬性指向一個數組,就意味着實例對象能夠調用數組方法。
constructor屬性
prototype對象有一個constructor屬性,默認指向prototype對象所在的構造函數。
constructor屬性的做用是,能夠得知某個實例對象,究竟是哪個構造函數產生的。

function P() {}
P.prototype.constructor === P // true constructor屬性定義在prototype對象上,意味着能夠被全部實例對象繼承。

let p = new P();
p.constructor === P // true p是構造函數P的實例對象
p.constructor === P.prototype.constructor // true
p.hasOwnPrototype('constructor') // false p自身沒有constructor屬性,該屬性是讀取原型鏈上面的P.prototype.constructor屬性

instanceof運算符
instanceof運算符返回一個布爾值,表示對象是否爲某個構造函數的實例。(左邊是實例對象,右邊是構造函數。)

let v = new Vehicle();
v instanceof Vehicle // true 實際檢查右邊構建函數的原型對象(prototype),是否在左邊對象的原型鏈上。
// 等同於
Vehicle.prototype.isPrototypeOf(v)

// instanceOf檢查的是整個原型鏈,所以同一個實例對象,可能會對多個構造函數都返回true
let d = new Date();
d instanceof Date // true
d instanceof Object // true d同時是Date和Object的實例

// 任意對象(除了null)都是Object的實例,因此instanceof運算符能夠判斷一個值是否爲非null的對象
typeof null // Object 是爲了防止這種狀況的發生
null instanceof Object // false

// 可是須要注意的是,instanceof運算符只能用於對象,不適用原始類型的值
let s = 'hello';
's' instanceof String // false
new String('s') instanceof String // true

// 對於undefined和null,instanceof運算符老是返回false
undefined instanceof Object // false
null instanceof Object // false

emm,對象的繼承中的模塊內容還須要下狠功夫
Object對象的相關方法
Object.getPrototypeOf()方法返回參數對象的原型,這是獲取原型對象的標準方法。

let F = function () {};
let f = new F();
Object.getPrototypeOf(f) === F.prototype // true

// 特殊對象的原型
// 空對象的原型是 Object.prototype
Object.getPrototypeOf({}) === Object.prototype // true
// Object.prototype 的原型是 null
Object.getPrototypeOf(Objectprototype) === null // true
// 函數的原型是 Function.prototype
function f() {}
Object.getPrototypeOf(f) === Function.prototype // true

Object.create()能夠從一個實例對象,生成另外一個實例對象。該方法接受一個對象做爲參數,而後以它爲原型,返回一個實例對象,該實例徹底繼承原型對象的屬性。

// 原型對象 沒有this,因此不是構造函數!!!不能用new來建立,這是一個實例對象。
let A = {
    print: function () {
        console.log('hello');
    }
}
// 實例對象
let B = Object.create(A); // 至關於建立一個空的構造函數,將其.prototype屬性指向參數對象A,從而實現讓該實例繼承A的屬性。

Object.getPrototypeOf(B) === A // true 以A對象爲原型,生成了B對象,B繼承了A的全部屬性和方法
B.print() // hello
B.print === A.print // true

// 如下三種方式生成的新對象是等價的
let obj1 = Object.create({});
let obj2 = Object.create(Object.prototype);
let obj3 = new Object();

//Object.create方法生成的新對象,動態繼承了原型。在原型上添加或修改任何方法,會馬上反映在新對象之上。
let obj1 = { p: 1 };
let obj2 = Object.create(obj1);
obj1.p = 2;
obj2.p // 2 修改對象原型obj1會影響到實例對象obj2
obj2.p = 3;
obj1.p // 1 修實例對象obj2並不會影響到原型對象obj1
obj2.p // 3

length用來截斷長度,只對數組有效,對字符串無效。
emm 面向對象的編程也是雲裏霧裏的,fighting!
異步操做概述
JavaScript只在一個線程上運行,也就是說,JavaScript同時只能執行一個任務,其餘任務都必須在後面排隊等待。
程序裏面全部的任務,能夠分紅兩類:同步任務(synchronous)和異步任務(asynchronous)。同步任務是那些沒有被引擎掛起、在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務。異步任務是那些被引擎放在一邊,不進入主線程、而進入任務隊列的任務,只有引擎認爲某個異步任務能夠執行了(好比Ajax操做從服務器獲得告終果),該任務(採用回調函數的形式)纔會進入主線程執行。也就是說,異步任務不具備「堵塞」效應。
JavaScript運行時,除了一個正在運行的主線程,引擎還提供了一個任務隊列(task queue),裏面是各類須要當前程序處理的異步任務。(實際上,根據異步任務的類型,存在多個任務隊列。爲了方便理解,這裏假設只存在一個隊列。)
引擎如何肯定異步任務有沒有結果,能不能進入主線程呢?答案就是引擎在不停地檢查,一遍又一遍,只要同步任務執行完了,引擎就會去檢查那些掛起來的異步任務,是否是能夠進入主線程了。這種循環檢查的機制,就叫作時間循環(Event Loop)。維基百科的定義是:「事件循環是一個程序結構,用於等待和發送消息和事件」(a programming construct that waits for and dispatches events or messages in a program)。
異步操做的模式:
一、回調函數 回調函數是異步操做最基本的方法。(包括Promise)

function f1() {
    // ...
}
function f2() {
    // ...
}

f1();
f2(); // 這樣編程的意圖是f2必須等到f1執行完成,才能執行。
// 可是若是f1是異步操做,f2會當即執行,不會等到f1結束再執行。這種狀況下,能夠考慮改寫f1,把f2寫成f1的回調函數。
function f1(callback) {
    // ...
    callback();
}
function f2() {
    // ...
}
f1(f2); // 回調函數的優勢是簡單、容易理解和實現,缺點是不利於代碼的閱讀和維護,各個部分之間高度耦合(coupling),使得程序結構混亂、流程難以追蹤(尤爲是多個回調函數嵌套的狀況),並且每一個任務只能指定一個回調函數。

二、事件監聽
之前只記着是定時器,如今才知道,應該是事件監聽!!!仍是須要多看!!!
另外一種思路是採用事件驅動模式。異步任務的執行不取決於代碼的順序,而取決於某個事件是否發生。

f1.on('done', f2); // 當f1發生done事件,就執行f2。接着,對f1進行改寫
function f1() {
    setTimeout(function () {
        // ...
        f1.trigger('done'); // 表示執行完成後,當即觸發done事件,從而開始執行f2
    }, 1000);
}

這種方法比較容易理解,能夠綁定多個事件,每一個事件能夠指定多個回調函數,而且能夠「去耦合」(decoupling),有利於實現模塊化。缺點是整個程序都要編程事件驅動型,運行流程會變得很不清晰。閱讀代碼的時候,很難看出主流程。
三、發佈/訂閱
事件徹底能夠理解成「信號」,若是存在一個「信號中心」,某個任務執行完成,就向信號中心「發佈」(publish)一個信號,其餘任務能夠向信號中心「訂閱」(subscribe)整個信號,從而知道何時本身能夠開始執行。這就叫作「發佈/訂閱模式」(publish-subscribe parttern),又稱「觀察者模式」(observer pattern)。
觀察者模式還不是瞭解,還須要學習!!

定時器
JavaScript提供定時執行代碼的能力,叫作定時器(timer),主要由setTimeout()和setInterval()這兩個函數來完成。
一、setTimeout()
該函數用來指定某個函數或某段代碼,再多少毫秒以後執行。它返回一個整數,表示定時器的編號,之後能夠用來取消這個定時器。
setTimeout函數接受兩個參數,第一個參數func|code是將要推遲執行的函數名或者一段代碼,第二個參數是推遲執行的毫秒數。

let timerId = setTimeout(func|code, delay);

console.log(1);
setTimeout('console.log(2)', 1000); // 須要注意的是,console.log(2)必須以字符串的形式,做爲setTimeout的參數
console.log(3);
// 1
// 3
// 2

function f() {
    console.log(2);
}
setTimeout(f, 1000); // 若是推遲執行的是函數,就直接將函數名,做爲setTimeout的參數。

二、setInterval()
該函數的用法與setTimeout徹底同樣,區別僅僅在於setInterval指定某個任務每隔一段時間就執行一次,也就是無限次的定時執行。

// 清除定時器
var id1 = setTimeout(f, 1000);
var id2 = setInterval(f, 1000);
clearTimeout(id1);
clearInterval(id2);

三、實例:debounce函數
debounce防抖動,這是個好東西,之後要好好看看!!感受能夠用到小程序以及h5的輸入框輸入!!
四、setTimeout(f, 0)
setTimeout的做用是將代碼推遲到指定時間執行,若是指定時間爲0,即setTimeout(f, 0),那麼會馬上執行嗎?答案是不會,由於它必需要等到當前腳本的同步任務,所有處理完之後,纔會執行setTimeout指定的回調函數f。也就是說,setTimeout(f, 0)會在下一輪事件循環一開始就執行。

setTimeout(function () {
    console.log(1);
}, 0);
console.log(2);
// 2
// 1

Promise對象
Promise對象是JavaScript的異步操做解決方案,爲異步操做提供統一接口。它起到代理做用(proxy),充當異步操做與回調函數之間的中介,使得異步操做具有同步操做的接口。
Promise對象的狀態:Promise對象經過自身的狀態,來控制異步操做。
Promise實例具備三種狀態:
1)異步操做未成功(pending)
2)異步操做成功(fulfilled)
3)異步操做失敗(rejected)
三種狀態裏面,fulfilled和rejected合在一塊兒稱爲resolved(已定型)。
這三種狀態的變化途徑只有兩種:從「未完成」到「成功」;從「未完成」到「失敗」。
一旦狀態發生變化,就凝固了,不會再有新的狀態變化。這也是Promise這個名字的又來,它的英文意思是「承若」,一旦承諾成效,就不得再改變了。這也意味着,Promise實例的狀態變化只可能發生一次。所以,Promise的最終結果只有兩種:
1)異步操做成功,Promise實例傳回一個值(value),狀態變爲fulfilled;
2)異步操做失敗,Promise實例拋出一個錯誤(error),狀態變爲rejected。
JavaScript提供原生的Promise構造函數,用來生成Promise實例。
Promise構造函數接受一個函數做爲參數,該函數的兩個參數分別是resolve和reject,它們是兩個函數,由JavaScript引擎提供,不用本身實現。
resolve函數的做用是,將Promise實例的狀態從「未完成」變爲「成功」(即從pending變爲fulfilled),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去。
reject函數的做用是,將Promise實例的狀態從「未完成」變爲「失敗」(即從pending變爲rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。

DOM概述DOM是JavaScript操做網頁的接口,全稱爲「文檔對象模型」(Document Object Model)。它的做用是將網頁轉爲一個JavaScript對象,從而能夠用腳本進行各類操做(好比增刪內容)。DOM的最小組成單位叫作節點(node)。文檔的屬性結構(DOM樹),就是由各類不一樣類型的節點組成,每一個節點能夠看做是文檔樹的一片葉子。節點的類型有七種:Document:整個文檔樹的頂層節點DocumentType:doctype標籤(好比<!DOCTYPE html>)Element:網頁的各類HTML標籤(好比<body>、<a>等)Attribute:網頁元素的屬性(好比class="right")Text:標籤之間或標籤包含的文本Comment:註釋DocumentFragment:文檔的片斷瀏覽器提供一個原生的節點對象Node,上面這七種節點都繼承了Node,所以具備一些共同的屬性和方法。emm 後面的看不下去了,再見!!!

相關文章
相關標籤/搜索