面向對象筆記

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

  • 類就是對某一些具備相同特徵與特性的抽象的描述
  • 實例相對比類,就是一個實實在在具體的事物。

什麼是僞數組?

  1. 僞數組是一個非數組類型對象設計模式

  2. 有一個length屬性,值爲number類型api

  3. 存在下標方式存儲數據數組

    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

給對象手動添加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未採納

  • 查找一個變量,優先找函數本身做用域內的變量,找不到就去調用該函數的做用域中去找, 按照這個規則直到全局都沒有找到,就報錯。

有一個容易搞混,又沒有什麼聯繫的知識點,這裏強調一下

  • 函數內的this,與函數的定義無關,與調用有關。
var obj = {
	fn: function() { console.log(this) };
};
var fn = obj.fn;

// 同一個fn,三種調用方式,this分別不一樣
obj.fn(); // obj
fn();     // window
new fn(); // fn實例
複製代碼
  • 變量的查找,與函數的定義有關,與調用無關。
function fn() {
	console.log(a); // 報錯,本身找不到,去定義fn的全局找,因此這裏和fn的定義有關,與fn的調用無關。
}
(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不屬於。

函數的定義方式

  • 字面量
    • 函數聲明
      • function fn(){}
    • 函數表達式
      • var fn = function(){}
  • 構造函數
    • new Function()

函數聲明

在js中,函數聲明式寫法比較單一,好區分。this

  • 必定是以function關鍵字開頭定義的函數
  • 必定具備函數名
  • 函數聲明有兩種,1種是全局函數聲明,1中是局部函數聲明
    • 函數聲明要麼在全局,要麼直接嵌套在另外一個函數內

函數表達式

在js中,函數表達式的編寫形式,多種多樣。 好比把函數看成數據賦值給變量,或者把函數做爲返回值return,或者當作參數傳遞,或者運算符運算,或者自調函數。

  • 要麼不是以function關鍵字開頭來定義的函數,要麼該函數定義在了語句當中
  • 函數名無關緊要

預解析細節規則

  • 變量聲明重名 -- 後面的忽略,沒有必要定義重複的變量
  • 函數聲明重名 -- 保留後面的,由於函數體可能不同,後面的優先與前面的
  • 變量與函數重名 -- 保留函數
  • 寫在代碼塊中的函數,名字會被預解析,函數體不會
    • 最終形成的現象是,在該函數定義的訪問它,不會報錯,獲得一個undefined
console.log(fn)  // undefined
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);  // 100
	var a = 200;
	console.log(a);  // 200
}(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);
複製代碼
// apply拆分數組或僞數組值依次傳遞給函數
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的函數

類成員&實例成員

  • 類成員
    • 添加給類本身的屬性與方法
  • 實例成員
    • 添加給實例本身的屬性與方法
    • 原型上供實例使用的屬性與方法
相關文章
相關標籤/搜索