1 -- 面向對象
對象
任何事物均可以看做是對象。javascript
如何使用js語言描述對象
字面量形式建立對象
var p = {};
p.name = '中國人';
p.age = '500';
複製代碼
構造函數形式建立對象
function Person(name, age) {
this.name = name;
this.age = age;
}
var p = new Person('中國人', 500);
var p2 = new Person('中國人2', 500);
複製代碼
面向對象與面向過程的區別
- 面向過程
- 面向對象
- 本身充當一個指揮者的角色,指揮更加專業的對象幫我解決問題。
面向過程的優缺點
- 缺點
- 代碼可讀性比較差
- 可維護性和可擴展性比較差
- 全局變量污染嚴重,變量管理混亂
- 優勢
- 書寫快速,由於一般是想到什麼寫什麼,只要能解決當前問題便可
- 一般來講比面向對象要節省內存
面向對象的優缺點
- 缺點
- 比面向過程要消耗一些內存
- 開發會比過程要慢一些(但也不是絕對的,由於面向對象書寫的代碼複用性比較強,也就是第一天比較慢,往後可能就比較快了)
- 優勢
- 代碼可讀性比較高
- 可維護性和可擴展性比較高
- 變量的管理比較清晰
面向對象代碼的書寫
- 要根據需求想象咱們須要那些對象幫我解決這個需求(好比我要逛街,須要一個導購,須要一個保鏢,須要一個女友)
- 編寫對象對應的構造函數(好比function Person(){})
- 抽取該對象所需的屬性,編寫到構造函數內(好比this.name = name)
- 抽取該對象所需的方法,編寫到構造函數原型中(好比Person.prototype.run = function(){})
- 把類寫完以後,就能夠建立實例,解決實際需求了
面向對象的3大特徵
- 封裝性
- js中的對象本質上就是鍵值對的集合,是一個複雜數據類型,能夠包含N多其餘數據,這就是js中對象的封裝性。
- 繼承性
- js中能夠經過某種方式,讓一個對象無條件訪問另外一個對象的屬性與方法,這就是繼承性。
- js中的全部對象,都具備一個原型對象,能夠無條件的訪問這個原型對象的屬性與方法,這就是js中對象的繼承性。
- 多態性
- 在js中,對象能夠隨時變化,對象繼承的對象也能夠被隨意改變,這些動態的變化能夠理解爲是js的多態。
類和實例
若是把類看做是模子,實例能夠看做是模子刻出來的東西。java
- 類就是對某一些具備相同特徵與特性的抽象的描述
- 實例相對比類,就是一個實實在在具體的事物。
什麼是僞數組?
-
僞數組是一個非數組類型對象設計模式
-
有一個length屬性,值爲number類型api
-
存在下標方式存儲數據數組
var obj = {
0:1,
1:'2sfd',
length:2
}
var obj2 = {
length:0
}
var obj3 = {
0:1,
1:'2sfd',
name:'abc',
length:3
}
複製代碼
2 -- 繼承
原型
- 原型自己是一個對象,這個對象的屬性與方法可供其餘對象。
- 任何對象都有成爲原型的潛質,下面的代碼就讓obj成爲了原型。
誰有原型
誰有prototype
給對象手動添加prototype能夠實現繼承嗎
- 沒什麼亂用,由於對象不能配合new關鍵字建立實例。
函數的特殊性
- 函數也是對象的一種,因此也有__proto__
- 函數能夠用來建立實例,又有prototype
如何訪問一個對象的原型
- 經過__proto__屬性(可是它是非標準屬性,不建議開發中使用)
- 經過constructor屬性獲得對象的構造函數,再訪問其prototype獲得原型
prototype的做用
__proto__的做用
prototype與__proto__聯繫
- 經過構造函數建立的實例,實例的__proto__默認爲構造函數的prototype,除此以外,沒有任何聯繫。
構造函數建立對象的4個步驟
- 建立一個新對象(本質上就是開闢了一塊內存空間)
- 設置新對象的原型
- 本質上就是在這塊內存空間中添加了一個__prot__屬性
- __proto__屬性值與構造函數的prototype有關
- 至關因而這樣給__proto__賦值的:新對象.proto = 構造函數.prototype
- 執行構造函數,執行時設置其this爲新實例
- 返回新實例的地址
對象的屬性訪問規則
優先從自身查找,找不到就去原型找,還找不到繼續去原型的原型找, 直到終點,終點也沒有返回undefined。閉包
對象的屬性賦值
本身沒有該屬性至關於新增,有則修改,並不會對其原型上的屬性形成影響。app
繼承
- 在js中,只要一個對象可以使用另外一個對象的成員,這種特徵就是繼承。
- 在主流的面嚮對象語言中,繼承是類與類之間的關係,在js中繼承是對象與對象之間的關係。
繼承方式
一、默認的原型繼承
function P() {}
P.prototype.fun = function(){};
var p = new P();
複製代碼
二、原型替換
function P() {}
P.prototype = {
constructor: P,
fun: function(){}
};
var p = new P();
複製代碼
三、Object.create
var proObj = {
fun: function(){}
};
var p = Object.create(proObj);
複製代碼
四、原型組合式
function A() {}
function P() {}
P.prototype = Object.create(A.prototype);
P.prototype = new A();
var p = new P();
複製代碼
屬性複製
在平常開發中,可能會存在實現多繼承的需求,上面的原型組合式就能夠完成這個需求。 可是原型組合式如何嵌套過多,對於屬性的查找效率是有影響的,並且過長的原型,也不利於維護。 對於實現多繼承,還有另一種解決方案,這種解決方案有一個表明,就是jQuery庫中提供的extend方法。函數
實現屬性複製函數封裝
function copy() {
var target = arguments[0];
for(var i = 1, len = arguments.length; i < len; i++) {
for(var key in arguments[i]) {
target[key] = arguments[i][key];
}
}
return target;
}
複製代碼
關於for in遍歷的補充
- 使用for in的方式遍歷對象的屬性,是沒法遍歷出js內置屬性的。
使用屬性copy的方式給原型添加屬性的優勢
- 不會覆寫構造函數默認的prototype,那麼對應的constructor屬性就不會丟失
- 能夠替代原型組合式的寫法
- 使用靈活簡單
3 -- 原型鏈
原型的規律
- 原型鏈的終點統一是Object.prototype
- 對象的原型和該對象的類型有關
- 好比Person的實例,原型是Person.prototype
- 好比Animal的實例,原型是Animal.prototype
- []的原型鏈結構
- [] ==> Array.prototype ==> Object.prototype ==> null
- {}的原型鏈結構
- {} ==> Object.prototype ==> null
- /abc/的原型鏈結構
- /abc/ ==> RegExp.prototype ==> Object.prototype ==> null
- Person的原型鏈結構
- Person ==> Function.prototype ==> Object.prototype ==> null
- Function的原型鏈結構
- Function ==> Function.prototype ==> Object.prototype ==> null
- Object的原型鏈結構
- Object ==> Function.prototype ==> Object.prototype ==> null
- 構造函數默認的prototype,它統一都繼承Object.prototype
- 好比Person.prototype,原型是Object.prototype
- 好比Animal.prototype,原型是Object.prototype
- 經過這個規則,能夠自由猜測出任意一個實例全部的原型
- 好比Book的實例,其原型結構爲: Book實例 ==> Book.protoype ==> Object.prototype ==> null
原型鏈
- 一個對象,全部由__proto__聯繫在一塊兒的原型,稱之爲這個對象的原型鏈。
如何研究一個對象的原型鏈結構
- 先經過__proto__獲得對象的原型
- 而後訪問這個原型的constructor屬性,肯定該原型的身份
- 而後繼續按照上訴兩個步驟,往上研究原型,最終就獲得了對象的原型鏈。
instanceof -- 運算符
- 做用:判斷一個對象的原型鏈中是否含有某個構造函數的prototype
- 語法:對象 instanceof 構造函數
- 返回值:boolean
hasOwnProperty -- 方法
- 做用:判斷一個屬性是否是本身的(不包含繼承的屬性)
- 語法:對象.hasOwnProperty(屬性名)
- 返回值:boolean
in -- 運算符
- 做用:判斷可否使用某個屬性(包含繼承的屬性)
- 語法:屬性名 in 對象
- 返回值:boolean
delete -- 運算符
- 做用:刪除對象的屬性
- 語法:delete 對象.屬性名 || delete 對象[屬性名]
- 返回值:boolean
Function -- 內置構造函數
- 做用:建立函數實例
- 語法:new Function(形參1,形參2,...,代碼體)
- 返回值:新建立的函數實例
- 特色:可以把字符串當作js腳本執行
eval -- 內置的全局函數
4 -- 做用域
做用域
變量的有效範圍。ui
如何檢測變量的有效範圍
- 在指定的做用域下訪問該變量,若是不報錯,就證實這個變量的有效範圍覆蓋了這個做用域。
全局變量
在js中如何定義全局變量
- 在函數外定義
- 或者不使用var定義的變量(這種方式不標準,儘可能不要使用)
局部變量
在js中如何定義局部變量
變量的生命週期
- 全局變量的生命週期從定義開始,到頁面關閉結束
- 局部變量的生命週期一般是從定義開始(函數被調用),到函數執行完畢結束(可是局部變量的生命週期可能由於閉包的存在被延長)
塊級做用域 ==> js未採納
- 凡是代碼塊就能夠產生新的做用域,代碼塊內的變量外界沒法使用。
函數做用域 ==> js採納
- 只有函數能夠產生新的做用域,函數內的變量外界沒法使用。
- js中是沒有塊級做用域的,只有函數做用域。
詞法做用域(靜態做用域) ==> js採納
- 查找一個變量,優先找函數本身做用域內的變量,找不到就去定義該函數的做用域中去找, 按照這個規則直到全局都沒有找到,就報錯。
動態做用域 ==> js未採納
- 查找一個變量,優先找函數本身做用域內的變量,找不到就去調用該函數的做用域中去找, 按照這個規則直到全局都沒有找到,就報錯。
有一個容易搞混,又沒有什麼聯繫的知識點,這裏強調一下
var obj = {
fn: function() { console.log(this) };
};
var fn = obj.fn;
obj.fn();
fn();
new fn();
複製代碼
function fn() {
console.log(a);
}
(function() {
var a = 10;
fn();
})();
複製代碼
做用域的產生
- 函數能夠被屢次重複調用,調用一次就會產生一個新的做用域,每個新做用域內會有新的變量。
做用域鏈
- 函數在定義的時候,未來它執行時的上級做用域就被肯定好了,上級做用域可能還有上級,函數全部的上級做用域稱之爲做用域鏈。
- 一個函數做用域能夠訪問的全部上級做用域,稱爲它的做用域鏈。
5 -- 預解析&閉包
預解析
- 能夠理解爲js解析引擎在逐行執行代碼前,對一些特殊代碼的預先執行。
- 也能夠認識是在馬拉松以前的熱身運動。
- 具體一點講,是js在逐行執行代碼前,會對js腳本進行一個總體檢查。
- 一、檢測語法有沒有錯誤
- 二、變量聲明提高:檢測到變量聲明那就率先進行聲明(其實是開闢一個內存空間,未來備用)
- 三、函數聲明提高:檢測到函數聲明也率先進行聲明(其實是開闢兩個內存空間,一個是變量,一個是函數)
- 預解析形成js一個特殊的象限,就是在變量聲明和函數聲明以前的代碼,能夠訪問它們。
- js預解析完畢以後,纔會總體正式逐行執行,可是預解析過的變量聲明和函數聲明不會重複執行。
- js預解析分爲兩種,全局預解析(全局代碼執行的時候會先預解析)與局部預解析(函數在調用的時候內部的代碼會先預解析)
變量聲明
- 使用經過var定義的變量,才屬於變量聲明
- 例如:var a; 屬於變量聲明。
- 例如:b = 10; 不屬於變量聲明。
- var關鍵字能夠經過逗號連續聲明多個變量
- 例如:var a, b, c = 20, d = 30;
- a,b,c,d所有屬於聲明。
- var關鍵字在聲明變量的時候,能夠給其賦值,若是賦值表達式中含有一些變量,這些變量不屬於變量聲明。
- 例如:var a = b = 10;
- 其中a屬於變量聲明,b不屬於。
函數的定義方式
函數聲明
在js中,函數聲明式寫法比較單一,好區分。this
- 必定是以function關鍵字開頭定義的函數
- 必定具備函數名
- 函數聲明有兩種,1種是全局函數聲明,1中是局部函數聲明
函數表達式
在js中,函數表達式的編寫形式,多種多樣。 好比把函數看成數據賦值給變量,或者把函數做爲返回值return,或者當作參數傳遞,或者運算符運算,或者自調函數。
- 要麼不是以function關鍵字開頭來定義的函數,要麼該函數定義在了語句當中
- 函數名無關緊要
預解析細節規則
- 變量聲明重名 -- 後面的忽略,沒有必要定義重複的變量
- 函數聲明重名 -- 保留後面的,由於函數體可能不同,後面的優先與前面的
- 變量與函數重名 -- 保留函數
- 寫在代碼塊中的函數,名字會被預解析,函數體不會
- 最終形成的現象是,在該函數定義的訪問它,不會報錯,獲得一個undefined
console.log(fn)
if(true) {
function fn(){}
}
console.log(fn)
複製代碼
- 函數表達式不會被預解析,可是函數表達式定義的函數執行時,其內部會對本身進行函數聲明。
- 最終形成的現象是,在該函數的外面沒法經過其名稱找到它,可是在內部能夠。
console.log(fn);
var a = function fn(){
console.log(fn);
}
console.log(fn);
複製代碼
函數執行時形參的賦值
- 一個函數在執行時,會優先定義形參,而後賦值。
- 預解析和逐行執行都慢與形參。
(function(a) {
console.log(a);
var a = 200;
console.log(a);
}(100));
複製代碼
閉包
- 有權訪問非自身局部變量(非全局變量)的函數,稱爲閉包。
- 有權訪問自由變量(非全局變量)的函數,稱爲閉包。
自由變量
- 一個函數能夠訪問的非自身內部變量,稱爲這個函數的自由變量。
引用了自由變量的閉包特色
閉包的應用
- 能夠利用閉包的結構去管理一些重要的變量,防止外界隨意對其修改
6 -- 函數的四種調用模式
this的特色
- 函數中的this,調用方式不一樣,指向不一樣
- this與調用有關,與定義無關
函數調用模式
- 函數名() || (function(){}()) ==> window
方法調用模式
- 對象.方法名() || 對象方法名 || 祖對象.父對象.子對象.方法名() ==> 宿主對象
構造器調用模式
- new 構造函數() || new 對象.構造函數() ==> new出來的新實例
間接調用模式(上下文調用模式)
- call
- 函數.call(指定的this,實參1,實參2,...)
- 對象.方法.call(指定的this,實參1,實參2,...)
- apply
- 函數.apply(指定的this,[實參1,實參2,...])
- 函數.apply(指定的this,{0: 實參1, 1:實參2, length: 2})
- 對象.方法.apply(指定的this,[實參1,實參2,...])
call和apply的使用範例
var obj = {};
Array.protype.push.call(obj, '要添加的第一個值', '要添加的第二個值')
複製代碼
var arr = [];
Object.prototype.toString.call(new Date).slice(8, -1)
複製代碼
function Parent(name, age) {
this.name = name;
this.age = age;
}
function Son() {
Parent.apply(this, arguments);
}
var p = new Son('火星人', 999);
複製代碼
var arr = [1, 10, 20, 40];
Math.max.apply(null, arr)
複製代碼
7 -- ES5新特性&設計模式&js高級補充
ES5數組新增的3個方法
forEach
- 做用:幫咱們遍歷數組,每遍歷到一個值,就會調用一次回調,把這個值與它的下標傳遞過去
- 語法:數組.forEach(function(v, i){ console.log('使用forEach幫咱們遍歷好的值與下標') })
- 返回值:undefined
map
- 做用:能夠用來代替forEach,可是map能夠接收回調的返回值,最終經過一組數據映射爲回調返回的另一組數據
- 語法:var mapArr = 數組.map(function(v, i){ return v * v })
- 返回值:回調全部的返回值組成的新數組
filter
- 做用:能夠用來代替forEach,可是還能夠過濾數組中的值
- 語法:var filterArr = 數組.filter(function(v, i){ if(v % 2 ==0){ return true; } })
- 返回值:全部返回回調返回true的對應值組成的新數組
call&apply的補充
- 若是不傳參 ==> this指向window
- 傳null ==> this指向window
- 傳undefined ==> this指向window
- 傳123 ==> this指向123的包裝類型對象(Number對象)
- 傳'abc' ==> this指向'abc'的包裝類型對象(String對象)
- 傳true ==> this指向true的包裝類型對象(Boolean對象)
- 傳對象 ==> this指向傳入的對象
構造函數的返回值
- 若是構造函數沒有return語句,那麼new它,獲得一個新實例
- 若是構造函數return了一些基本類型數據,那麼new它,獲得一個新實例
- 若是構造函數return了一個對象,那麼new它,獲得return的對象
嚴格模式
- ES5新增的一個特性,使用該特性可讓js以一種新的模式運行js腳本。
- 該模式下能夠強制咱們拋棄那些不推薦不友好的寫法
- 該模式下可讓js以前的一些設計不太合理的api表現的合理一些
- 該模式下可讓js擁有一些新的特性,好比ES6/ES7規範中定義的某些語法,必須在嚴格模式下才有效
嚴格模式的分類
- 全局模式
- 在全局代碼的最上面書寫一句話'use strict';
- 使用該模式,全部的代碼都按照嚴格模式執行
- 局部模式
- 在函數內部的最上面書寫一句話'use strict';
- 使用該模式,只有該函數內的代碼纔會按照嚴格模式執行
須要記住的幾條嚴格模式規則
- 定義變量必須使用var
- 函數調用模式this爲undefined
- 真正實現了call誰this就爲誰
- eval擁有了單獨的做用域
設計模式
沙箱模式
- 使用某種方式,防止一些代碼對外界環境形成潛在影響,這類代碼就能夠認爲是沙箱模式
- 在js中,最簡單的沙箱模式寫法,就是使用一個自調函數把某塊代碼進行封裝,能夠防止全局變量污染
工廠模式
- 凡是調用一個函數,函數返回一個對象,那麼這個函數就能夠認爲是一個工廠。
- 在js中,通常的工廠都是把new對象的過程進行了一個封裝。
單例模式
- 只要讓某種類型的實例,只能存在一個,實現這種需求的代碼就是單例模式
- 在js中,JSON與Math對象就被設計爲單例模式,咱們不可以建立第二個這種類型的對象。
觀察者模式 -- 發佈訂閱模式
- 只要某事件發生後,會自動執行預設好的回調,那麼實現這種需求的代碼就是觀察者模式
- 觀察者模式能夠認爲就是自定義事件
- 觀察者模式一般使用的場景是這樣的:某對象作了某件事,其餘對象作出相應的一個響應
bind
- ES5提供了一個新的能夠改變函數this指向的新函數
- 做用:經過某函數獲得一個綁定了固定this的新函數,這個新函數能夠是舊函數的clone版本
- 語法:var bindFn = 函數.bind(this)
- 返回值:綁定了this的函數
類成員&實例成員
- 類成員
- 實例成員
- 添加給實例本身的屬性與方法
- 原型上供實例使用的屬性與方法