JavaScript高級與面向對象

對象:任何事物均可以看做是對象。

一、面向對象與面向過程的概念

  • 面向過程:凡是本身親力親爲,本身循序漸進的解決現有問題。
  • 面向對象:本身充當一個指揮者的角色,指揮更加專業的對象幫我解決問題。
  • 聯繫:面向對象仍然離不開面向過程,能夠認爲它是對面向過程更高一層的封裝。

二、建立對象的方式

  •  字面量形式

    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);

     

 

三、構造函數

  • 概念

  • 若是一個函數配合new關鍵字建立對象,那麼這個函數也叫構造函數
    1. 構造函數與普通函數本質上是同樣的
    2. 編寫構造函數時,首字母一般會大寫,但不是必須的(相似變量駝峯命名法)
  •  返回值特色

  1. 若是構造函數沒有return語句,那麼new它,獲得一個新實例
  2. 若是構造函數return了一些基本類型數據,那麼new它,獲得一個新實例
  3. 若是構造函數return了一個對象,那麼new它,獲得return的對象

四、類與實例的概念

  • 類是對一些具備相同特徵與特性事物的抽象描述
    1. 好比動物類的定義是比較抽象的,它抽取了動物與動物之間的相同特徵。
    2. 一樣植物類、哺乳動物類、人類的定義也都是比較抽象的,也是提取他們的共同特徵而造成的定義,這就是類。
    3. 在js中,能夠把構造函數看做是類
  • 實例

  • 實實在在的具體事物就是某個類的實例。
    1. 好比我家的旺財,是狗類的實例
    2. 個人弟弟妹妹,是人類的實例
    3. 在js中,經過構造函數建立的對象就是實例

聯繫:若是把類看做是模子,實例則是模子印出來的東西。

  •  對象類型

  • 對象的類型就是其構造函數的名字
    1. 好比數組是Array類型的對象,日期是Date類型的對象
    2. Array和Date就是其構造函數的名字
    3. 那麼經過Person建立一個實例,那麼這個實例就是Person類型的對象。

五、 原型

  • 原型是一個對象,它的屬性能夠供其餘對象共享
    1. js中有不少原型對象,基本每一個對象都有屬於本身的原型
    2. 原型對象的存在能夠大大的節省內存開銷
  •  原型的使用

  1. 每一個構造函數都有一個prototype屬性,能夠給其賦值
  2. 而後經過構造函數建立的實例就能夠共享其屬性與方法

六、面向對象與面向過程優缺點

  • 面向對象

  • 缺點
    1. 一般比面向過程消耗內存,由於有不少實例要存儲
    2. 前期開發比較緩慢,可是複用性強,後期開發與維護進度會逐漸加快
    優勢
    1. 變量的管理比較清晰,可讀性較高
    2. 由於代碼與對象間的職責比較清晰,因此後期可維護性和可擴展性也比較高
    3. 複用性更強
  •  面向過程

  • 缺點
    1. 變量混亂,可讀性較差
    2. 一般有新需求出現,代碼改動比較大,因此可維護性和可擴展性比較差
    優勢:開發迅速,只要能解決當前問題便可

七、面向對象3大特徵

  1. 封裝性:對象能夠把不少屬性與方法集中在一塊兒管理,就是js的封裝性。
  2. 繼承性:對象可使用其原型對象的屬性與方法,就是js的繼承性。
  3. 多態性:js沒有多態。若是非要說,那麼對象形態、繼承關係能夠隨時被改變,能夠認爲是js的多態性。

八、 面向對象的書寫過程

  1. 根據需求提取解決該問題所需的對象
    • 好比我要逛街,須要一個導購,須要一個保鏢,須要一個女友
  2. 編寫每個對象所對應的構造函數
    • 構造函數能夠重複性建立實例,由於我可能須要多個保鏢 function Person() {}
  3. 抽取對象所需的屬性
    • 就是該對象應該擁有的特徵,好比人有名稱、年齡、性別、四肢、雙眼。 function Person(name) { this.name = name; }
  4. 抽取對象所需的方法
    • 就是該對象應該擁有的特性,好比人會學習創造,狗會看門逗你笑 Person.prototype.study = function(){};
  5. 根據寫好的構造函數建立實例,調用屬性方法解決實際需求
    • 就是調度實例幹事 var p = new Person(); p.study(); 

九、 原型其餘

  • 誰有prototype與proto

  1. 每一個函數都有prototype屬性
  2. 每一個對象都有proto屬性
  3. 函數比較特殊,便是函數又是對象,因此prototype與proto都有
  • prototype與proto聯繫

  1. 經過構造函數建立的實例
  2. 當前構造函數的prototype屬性指向誰,實例的proto屬性就指向誰
  • 如何獲得一個對象繼承的原型

  1. 經過proto屬性(可是它是非標準屬性,不建議開發中使用)
  2. 經過constructor屬性獲得對象的構造函數,再訪問其prototype獲得原型
  • 建立對象時內在的4個步驟

  1. 建立一個新實例(本質上就是開闢了一塊內存空間)
  2. 設置新對象的原型執行構造函數,執行時設置其this指向新實例
    • 給新實例設置proto屬性值
    • 這個值與構造函數的prototype屬性有關
    • 賦值過程至關於這樣:新實例.proto = 構造函數.prototype
  3. 返回新實例的地址
  • 對象的屬性訪問規則

  1. 優先從自身查找
  2. 找不到就去原型找
  3. 還找不到繼續去原型的原型找
  4. 直到終點,終點也沒有返回undefined
  •  對象的屬性賦值

  1. 給一個對象的屬性賦值
  2. 若是以前沒有該屬性那麼就是新增,有就是修改
  3. 對象的屬性賦值隻影響本身,不會對其餘對象和原型對象形成影響

十、原型常見書寫方式

  • 默認原型

    function P() {} P.prototype.fun = function(){}; var p = new P();
  • 置換原型

    function P() {} P.prototype = { constructor: P, fun: function(){} }; var p = new P();
  • extend複製擴展

    function P() {} extend(P.prototype, {}, { fun: function(){} }, { fun2: function(){} }); var p = new P();
  • Object.create

    var proObj = { fun: function(){} }; var p = Object.create(proObj);
  • 實現屬性複製函數封裝

    function extend() { 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; }
  • 類成員與實例成員

  1. 類成員(靜態成員):添加給類本身的屬性與方法
  2. 實例成員
    • 添加給實例本身的屬性與方法
    • 原型上供實例使用的屬性與方法

 

十一、原型鏈

 

  • 概念:一個對象繼承的全部由proto屬性串聯在一塊兒的對象,稱爲該對象的原型鏈。

  • 對象原型鏈的研究方案

  1. 先經過proto獲得對象的原型
  2. 而後訪問這個原型的constructor屬性,肯定該原型的身份
  3. 而後繼續按照上訴兩個步驟,往上研究原型,最終就獲得了對象的原型鏈。
  •   規律與常見對象原型鏈結構

  1. 原型鏈的終點統一是Object.prototype
  2. 對象的原型和該對象的類型有關
    1. 好比Person的實例,原型是Person.prototype
    2. 好比Animal的實例,原型是Animal.prototype
    3. []的原型鏈結構 [] ==> Array.prototype ==> Object.prototype ==> null 
    4. {}的原型鏈結構 {} ==> Object.prototype ==> null 
    5. /abc/的原型鏈結構 /abc/ ==> RegExp.prototype ==> Object.prototype ==> null 
    6. Person的原型鏈結構 Person ==> Function.prototype ==> Object.prototype ==> null 
    7. Function的原型鏈結構 Function ==> Function.prototype ==> Object.prototype ==> null 
    8. Object的原型鏈結構 Object ==> Function.prototype ==> Object.prototype ==> null 
  3. 構造函數默認的prototype,它統一都繼承Object.prototype
    • 好比Person.prototype,原型是Object.prototype
    • 好比Animal.prototype,原型是Object.prototype
  4. 經過這個規則,能夠自由猜測出任意一個實例全部的原型
    • 好比Book的實例,其原型結構爲: Book實例 ==> Book.protoype ==> Object.prototype ==> null

十二、運算符與方法

  • in -- 運算符

  1. 做用:判斷可否使用某個屬性(包含繼承的屬性)
  2. 語法:屬性名 in 對象
  3. 返回值:boolean
  •  hasOwnProperty -- 方法

  1. 做用:判斷一個屬性是否是本身的(不包含繼承的屬性)
  2. 語法:對象.hasOwnProperty(屬性名)
  3. 返回值:boolean
  • 關於for in遍歷的補充:for in能夠遍歷對象繼承的屬性,不過一些內置的屬性是不可遍歷的。

  • delete -- 運算符

  1. 做用:刪除對象的屬性
  2. 語法:delete 對象.屬性名 || delete 對象[屬性名]
  3. 返回值:boolean
  • instanceof -- 運算符

  1. 做用:判斷一個對象的原型鏈中是否含有某個構造函數的prototype
  2. 語法:對象 instanceof 構造函數
  3. 返回值:boolean
  •  Function -- 內置構造函數

  1. 做用:建立函數實例
  2. 語法:new Function(形參1,形參2,...,代碼體)
  3. 返回值:新建立的函數實例
  4. 特色:可以把字符串當作js腳本執行
  • eval -- 內置的全局函數

  1. 做用:執行字符串代碼
  2. 語法:eval(字符串代碼)

1三、 函數四種調用模式

謹記:函數調用時,內部的this的值和這個函數定義無關,和運行(調用)有關。api

  • 函數調用模式 ==> 函數名() || 自調

這種方式調用,函數運行時內部的this指向全局對象window。數組

  • 方法調用模式 ==> 對象.方法名() || 對象['方法名'] || 祖對象.父對象.子對象.方法名()

這種方式調用,函數運行時內部的this指向宿主對象。 (dom中事件綁定的函數,就是這種調用方式,因此this指向對應的dom對象)緩存

  • 構造函數調用模式 ==> new 構造函數() || new 對象.構造函數()

這種方式調用,函數運行時內部的this指向新建立的實例對象。閉包

  • 上下文調用模式(間接調用模式)

  1. 函數名.call(this, arg1, arg2);
  2. 函數名.apply(this, [arg1, arg2]); 這種方式調用,函數運行時內部的this指向call或apply傳入的第一個參數; 若是沒有傳第一個參數,或者第一個參數爲null、undefined,那麼this統一指向window。

1四、 做用域

 概念:變量的有效範圍。

  • 全局變量

  1. 在全局都有效的變量。
  2. 定義方式:函數外定義。
  3. 生命週期:從定義開始,到頁面被卸載結束。
  • 局部變量

  1. 只在局部有效的變量。
  2. 定義方式:函數內定義。
  3. 生命週期:通常狀況下,是從定義開始,到函數執行完畢結束。
  • 函數做用域

  1. 只有函數才能夠產生新的做用域
  2. 只有函數能夠限定變量的有效範圍
  •  塊級做用域 ==> js沒有

  1. 凡是代碼塊就能夠產生新的做用域
  2. 凡是代碼塊就能夠限定變量的有效範圍
  • 詞法做用域(靜態做用域)

  說的是變量的查找規則,特色是變量查找與函數定義有關,與調用無關app

  1. 先在當前做用域查找
  2. 找不到就去定義該函數的做用域找
  3. 一直找到全局做用域爲止,全局也沒有則報錯
  • 做用域的產生:函數能夠被屢次重複調用,調用一次就會產生一個新的做用域。

  • 做用域鏈

  1. 函數在定義的時候,未來它執行時的上級做用域就被肯定好了,上級做用域可能還有上級,函數全部的上級做用域稱之爲做用域鏈。
  2. 一個函數做用域能夠訪問的全部上級做用域,稱爲它的做用域鏈。
  •  垃圾回收機制原則

  1. 一個對象沒有被其餘變量或者屬性引用,那麼就會被釋放。 同時還要保證該對象可以被使用,對於那些沒法使用,又存在循環引用的對象,也會被釋放。
  2. 一個局部變量沒有被其餘函數引用,那麼就會被釋放。
  • 注意:有一個容易搞混,又沒有什麼聯繫的知識點,這裏強調一下

  • 函數內的this,與函數的定義無關,與調用有關。
function fn() { console.log(a); // 報錯,本身找不到,去定義fn的全局找,因此這裏和fn的定義有關,與fn的調用無關。
} (function() { var a = 10; fn(); })();

 

1五、閉包

  • 概念:在js中訪問了自由變量的函數就是閉包

  • 自由變量:函數可訪問的外部局部變量,稱之爲該函數的自由變量

  • 特色:閉包的自由變量生命週期會被拉長,與閉包的生命週期進行了捆綁

  • 計數器案例

    function getCounter() { var total = 0; return { add: function() { total++; }, get: function() { return total; } }; }; var counter = getCounter(); counter.add(); counter.get(); var counter2 = getCounter(); counter2.add(); counter2.get();

     

  •  緩存操做

    var cache = (function() { var cache = {}; return { set: function(key, val) { cache[key] = val; }, get: function(key) { return cache[key]; } }; }()); cache.set('張銳', '中國人'); cache.get('張銳');

     

  •  for循環練習

    var arr = ['第一句話', '第二句話', '第三句話']; for(var i = 0, len = arr.length; i < len; i++) { setTimeout(function(i) { return function() { console.log(arr[i]); } }(i), 1000 * i + 1000); }

     

1六、 預解析

  1. 能夠理解爲js解析引擎在逐行執行代碼前,對一些特殊代碼的預先執行。
  2. 預解析事後代碼纔會從上到下逐行執行,可是預解析時已經定義的變量與函數,是不會重複定義的。
  3. 預解析的本質就是變量對象初始化。
  • 預解析作了兩件事情

  一、變量聲明提高:檢測到變量聲明那就率先進行聲明dom

  二、函數聲明提高:檢測到函數聲明也率先進行聲明函數

  • 變量聲明

  1. 使用經過var定義的變量,才屬於變量聲明
    var a; //屬於變量聲明。
    b = 10;// 不屬於變量聲明。
  2. var關鍵字能夠經過逗號連續聲明多個變量
    var a, b, c = 20, d = 30; //a,b,c,d所有屬於聲明。
  3. var關鍵字在聲明變量的時候,能夠給其賦值,若是賦值表達式中含有一些變量,這些變量不屬於變量聲明。
    var a = b = 10; //其中a屬於變量聲明,b不屬於。
  • 函數聲明

  • 在js中,函數聲明式寫法比較單一,好區分。
    1. 要麼定義在全局
    2. 要麼定義在另外一個函數主體內
  • 預解析的特色

  1. 在變量聲明以前訪問它不會報錯
  2. 在函數聲明以前調用它不會報錯
  • 預解析相關細節

  1. js預解析分全局預解析與局部預解析,區別在於局部預解析在函數調用時發生。
  2. 變量聲明重名 -- 最終只留一個
      console.log(a); // 預解析後值保留一個變量a,值爲undefined
      var a = 1; var a = 2;
  3. 函數聲明重名 -- 保留後面的函數
     console.log(test); // 預解析後test爲打印2的函數
      function test(){ console.log(1) } function test(){ console.log(2) }
  4. 變量與函數重名 -- 保留函數
    console.log(test); // 預解析後test值爲函數
      var test = 10; function test(){} var test = 20;
  • 函數執行時形參會優先執行

    形參定義與賦值優先於變量與函數聲明。學習

(function(a) { console.log(a); // a函數
  var a = 200; function a(){} console.log(a); // 200
}(100));
  • 函數表達式的名稱

    // 函數fnName的名字在外面沒法訪問,可是能夠在函數內訪問,
        // 至關於本身的一個局部變量,值爲本身的引用。
        var fn = function fnName(){ console.log(fnName); // 裏面能夠訪問
     }; console.log(fnName); // 外面訪問報錯

1七、 函數的四種調用模式

  • this的特色

  1. 函數中的this,調用方式不一樣,指向不一樣
  2. this與調用有關,與定義無關
  • 1.17.1.1. 函數調用模式

    函數名() || (function(){}()) ==> windowthis

  • 方法調用模式

    對象.方法名() || 對象方法名 || 祖對象.父對象.子對象.方法名() ==> 宿主對象spa

  • 構造器調用模式

    new 構造函數() || new 對象.構造函數() ==> new出來的新實例

  • 間接調用模式(上下文調用模式)

  1. call
    • 函數.call(指定的this,實參1,實參2,...)
    • 對象.方法.call(指定的this,實參1,實參2,...)
  2. apply
    • 函數.apply(指定的this,[實參1,實參2,...])
    • 函數.apply(指定的this,{0: 實參1, 1:實參2, length: 2})
    • 對象.方法.apply(指定的this,[實參1,實參2,...])
  3. 異同
    • call與apply都來自Function.prototype,因此全部的函數均可以使用。
    • 均可以改變函數this的指向
    • 不一樣之處在於傳參的方式上
  4. 補充
    • 函數調用call和apply,其實是間接調用本身
    • 例如fn.call(),表面上看是調用call方法
    • 實際上連fn本身也被調用了,和fn()直接調用的區別是,this變了
  •  call和apply方法借用的原理

  1. 若是一個方法內部操做的是this,那麼咱們就能夠經過call或apply指定該方法this, this改變成誰,那麼該方法最終操做的就是誰。
  2. 若是一個方法內部沒有操做this,那麼是沒法借用的。
  • call和apply常見的使用場景

 1.借用數組方法操做僞數組
// 給僞數組添加數據
var obj = {}; Array.protype.push.call(obj, '要添加的第一個值', '要添加的第二個值') // 經過僞數組獲取對應的真數據(獲取後原僞數組不會被改變,只是獲得了新數組)
var argArr = [].slice.call(arguments);
二、借用Object.prototype.toString獲取對象類型
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)

1八、ES5數組新增的3個方法

  • forEach

  1. 做用:幫咱們遍歷數組,每遍歷到一個值,就會調用一次回調,把這個值與它的下標傳遞過去
  2. 語法:數組.forEach(function(v, i){ console.log('使用forEach幫咱們遍歷好的值與下標') })
  3. 返回值:undefined
  • map

  1. 做用:能夠用來代替forEach,可是map能夠接收回調的返回值,最終經過一組數據映射爲回調返回的另一組數據
  2. 語法:var mapArr = 數組.map(function(v, i){ return v * v })
  3. 返回值:回調全部的返回值組成的新數組
  •  filter

  1. 做用:能夠用來代替forEach,可是還能夠過濾數組中的值
  2. 語法:var filterArr = 數組.filter(function(v, i){ if(v % 2 ==0){ return true; } })
  3. 返回值:全部返回回調返回true的對應值組成的新數組

1九、 call&apply的補充

  1. 若是不傳參 ==> this指向window
  2. 傳null ==> this指向window
  3. 傳undefined ==> this指向window
  4. 傳123 ==> this指向123的包裝類型對象(Number對象)
  5. 傳'abc' ==> this指向'abc'的包裝類型對象(String對象)
  6. 傳true ==> this指向true的包裝類型對象(Boolean對象)
  7. 傳對象 ==> this指向傳入的對象

20、 嚴格模式

  1. ES5新增的一個特性,使用該特性可讓js以一種新的模式運行js腳本。
  2. 該模式下能夠強制咱們拋棄那些不推薦不友好的寫法
  3. 該模式下可讓js以前的一些設計不太合理的api表現的合理一些
  4. 該模式下可讓js擁有一些新的特性,好比ES6/ES7規範中定義的某些語法,必須在嚴格模式下才有效
  •  嚴格模式的分類

    全局模式

    1. 在全局代碼的最上面書寫一句話'use strict';
    2. 使用該模式,全部的代碼都按照嚴格模式執行
  1. 局部模式
    1. 在函數內部的最上面書寫一句話'use strict';
    2. 使用該模式,只有該函數內的代碼纔會按照嚴格模式執行
  • 須要記住的幾條嚴格模式規則

  1. 定義變量必須使用var
  2. 函數調用模式this爲undefined
  3. 真正實現了call誰this就爲誰

 其餘

  1. 不能使用with語句
  2. 廢除函數.caller與arguments.callee
  3. eval擁有了單獨的做用域
相關文章
相關標籤/搜索