摘要:一面向?qū)ο蟾拍蠲嫦驅(qū)ο缶褪鞘褂脤ο蟆R虼嗽跇?gòu)造函數(shù)中表示剛剛創(chuàng)建出來的對象。在構(gòu)造函數(shù)中利用對象的動態(tài)特性為其對象添加成員。
一、面向?qū)ο?/b> 1.1 概念
面向?qū)ο缶褪鞘褂脤ο蟆C嫦驅(qū)ο箝_發(fā)就是使用對象開發(fā)。
面向過程就是用過程的方式進行開發(fā)。面向?qū)ο笫菍γ嫦蜻^程的封裝。
1.2 三大特性抽象性
所謂的抽象性就是:如果需要一個對象描述數(shù)據(jù),需要抽取這個對象的核心數(shù)據(jù)
提出需要的核心屬性和方法
不在特定的環(huán)境下無法明確對象的具體意義
封裝性
對象是將數(shù)據(jù)與功能組合到一起,即封裝
JS對象就是鍵值對的集合,鍵值如果是數(shù)據(jù)(基本數(shù)據(jù)、符合數(shù)據(jù)、空數(shù)據(jù))就稱為屬性,如果鍵值是函數(shù)那么就稱為方法
對象就是將屬性與方法封裝起來
方法是將過程封裝起來
繼承性
所謂繼承性就是自己沒有但是別人有,拿過來成為自己的,就是繼承,繼承是實現(xiàn)復(fù)用的一種手段
在Java等語言中繼承滿足一個class的規(guī)則,類是一個class,他規(guī)定了一個對象有什么屬性和方法。
在這些語言中繼承是class之間的繼承,一個class繼承另一個class,那么該class就有了另一個class的成員,那么由該class創(chuàng)建出來的對象就同時具有兩個class的成員。
在JS中沒有明確的繼承語法(ES6提供了class extend語法),一般都是按照繼承的理念實現(xiàn)對象的成員擴充實現(xiàn)繼承,因此JS中實現(xiàn)繼承的方法非常對多。
傳統(tǒng)繼承基于類,JS繼承基于對象
一個簡單的繼承模式:混入(mix)
function mix ( o1, o2 ) { for ( var k in o2 ) { o1[ k ] = o2[ k ]; } }1.3 關(guān)于面向?qū)ο蟮囊恍┢渌拍?/b>
類class:在JS中就是構(gòu)造函數(shù)
在傳統(tǒng)的面向?qū)ο笳Z言中,使用一個叫類的東西定義模板,然后使用模板創(chuàng)建對象。
在構(gòu)造方法中也具有類似的功能,因此也稱其為類
實例(instance)與對象(object)
實例一般是指某一個構(gòu)造函數(shù)創(chuàng)建出來的對象,我們稱為XXXX 構(gòu)造函數(shù)的實例
實例就是對象。對象是一個泛稱
實例與對象是一個近義詞
鍵值對與屬性和方法
在JS中鍵值對的集合稱為對象
如果值為數(shù)據(jù)(非函數(shù)),就稱該鍵值對為屬性
如果值為函數(shù)(方法),就稱該鍵值對為方法method
父類與子類(基類和派生類)
傳統(tǒng)的面向?qū)ο笳Z言中使用類來實現(xiàn)繼承那么就有父類、子類的概念
父類又稱為基類,子類又稱為派生類
在JS中沒有類的概念,在JS中常常稱為父對象,子對象,基對象,派生對象。
二、構(gòu)造函數(shù) 2.1 構(gòu)造函數(shù)是干什么用的初始化數(shù)據(jù)的
在JS中給對象添加屬性用的,初始化屬性值用的
2.2 創(chuàng)建對象的過程代碼:var p = new Person();
首先運算符new創(chuàng)建了一個對象,類似于{},是一個沒有任何(自定義)成員的對象。
使用new 創(chuàng)建對象,那么對象的類型就是創(chuàng)建他的構(gòu)造函數(shù)名
使用{}無論如何都是Object類型,相當(dāng)于new Object
然后調(diào)用構(gòu)造函數(shù),為其初始化成員
構(gòu)造函數(shù)在調(diào)用的一開始,有一個賦值操作,即this = 剛剛創(chuàng)建出來的對象。
因此在構(gòu)造函數(shù)中this表示剛剛創(chuàng)建出來的對象。
在構(gòu)造函數(shù)中 利用 對象的動態(tài)特性 為其對象添加成員。
三、作用域 3.1 什么是作用域域表示的就是范圍,即作用域,就是一個名字在什么地方可以使用,什么時候不能使用。
簡單的說,作用域是針對變量的,比如我們創(chuàng)建一個函數(shù) a1,函數(shù)里面又包了一個子函數(shù) a2。
// 全局作用域 functiona a1() { // a1作用域 function a2() { // a2作用域 } }
此時就存 在三個作用域:全局作用域,a1 作用域,a2 作用域;即全局作用域包含了 a1 的作用域,a2 的作用域包含了 a1 的作用域。
當(dāng) a2 在查找變量的時候會先從自身的作用域區(qū)查找,找不到再到上一級 a1 的作用域查找,如果還沒找到就
到全局作用域區(qū)查找,這樣就形成了一個作用域鏈。
函數(shù)允許訪問函數(shù)外部的數(shù)據(jù)
整個代碼結(jié)構(gòu)中只有函數(shù)可以限定作用域
作用規(guī)則首先使用提升規(guī)則分析
如果當(dāng)前作用域中有了名字了,就不考慮外面的名字
3.3 屬性搜索原則所謂的屬性搜索原則,就是對象在訪問屬性或方法的時候,首先在當(dāng)前對象中查找
如果當(dāng)前對象中存儲著屬性或方法,停止查找,直接使用該屬性或方法
如果當(dāng)前對象沒有該成員,那么再在其原型對象中查找
如果原型對象中含有該成員,那么停止查找,直接使用
如果原型中還沒有,就到原型的原型中查找
如此往復(fù),直到Object.protitype還沒有,那么就返回undefined
如果是調(diào)用方法就報錯,該xxx不是一個函數(shù)
四、閉包 4.1 說說你對閉包的理解實用閉包主要是為了設(shè)計私有方法和變量。閉包的優(yōu)點是可以避免全局變量的污染;缺點是閉包會常駐內(nèi)存,增加內(nèi)存使用量,使用不當(dāng)很容易造成內(nèi)存泄露。在JavaScript中,函數(shù)即閉包,只有函數(shù)才能產(chǎn)生作用域。
閉包有3個特性:
函數(shù)嵌套函數(shù)
在函數(shù)內(nèi)部可以引用外部的參數(shù)和變量
參數(shù)和變量不會以垃圾回收機制回收
4.2 閉包有什么用(特性)閉包的作用,就是保存自己私有的變量,通過提供的接口(方法)給外部使用,但外部不能直接訪問該變量。
通過使用閉包,我們可以做很多事情,比如模擬面向?qū)ο蟮拇a風(fēng)格;更優(yōu)雅,更簡潔的表達出代碼;在某些方面提升代碼的執(zhí)行效率。利用閉包可以實現(xiàn)如下需求:
匿名自執(zhí)行函數(shù)
一個匿名的函數(shù),并立即執(zhí)行它,由于外部無法引用它內(nèi)部的變量,因此在執(zhí)行完后很快就會被釋放,關(guān)鍵是這種機制不會污染全局對象。
緩存
閉包正是可以做到這一點,因為它不會釋放外部的引用,從而函數(shù)內(nèi)部的值可以得以保留。
實現(xiàn)封裝
模擬面向?qū)ο蟮拇a風(fēng)格
4.3 閉包的基本模型對象模式
函數(shù)內(nèi)部定義個一個對象,對象中綁定多個函數(shù)(方法),返回對象,利用對象的方法訪問函數(shù)內(nèi)的數(shù)據(jù)
function createPerson() { var __name__ = ""; return { getName: function () { return __name__; }, setName: function( value ) { // 如果不姓張就報錯 if ( value.charAt(0) === "張" ) { __name__ = value; } else { throw new Error( "姓氏不對,不能取名" ); } } } } var p = createPerson(); p.set_Name( "張三豐" ); console.log( p.get_Name() ); p.set_Name( "張王富貴" ); console.log( p.get_Name() );
函數(shù)模式
函數(shù)內(nèi)部定義一個新函數(shù),返回新函數(shù),用新函數(shù)獲得函數(shù)內(nèi)的數(shù)據(jù)
function foo() { var num = Math.random(); function func() { return mun; } return func; } var f = foo(); // f 可以直接訪問這個 num var res1 = f(); var res2 = f();
沙箱模式
沙箱模式就是一個自調(diào)用函數(shù),代碼寫到函數(shù)中一樣會執(zhí)行,但是不會與外界有任何的影響,比如jQuery
(function () { var jQuery = function () { // 所有的算法 } // .... // .... jQuery.each = function () {} window.jQuery = window.$ = jQuery; })(); $.each( ... )4.4 閉包的性能問題
js 垃圾回收機制,也就是當(dāng)一個函數(shù)被執(zhí)行完后,其作用域會被收回,如果形成了閉包,執(zhí)行完后其作用域就不會被收回。
函數(shù)執(zhí)行需要內(nèi)存,那么函數(shù)中定義的變量,會在函數(shù)執(zhí)行結(jié)束后自動回收,凡是因為閉包結(jié)構(gòu)的,被引出的數(shù)據(jù),如果還有變量引用這些數(shù)據(jù)的話,那么這些數(shù)據(jù)就不會被回收。因此在使用閉包的時候如果不使用某些數(shù)據(jù)了,一定要賦值一個null
var f = (function () { var num = 123; return function () { return num; }; })(); // f 引用著函數(shù),函數(shù)引用著變量num // 因此在不使用該數(shù)據(jù)的時候,最好寫上 f = null;五、原型 5.1 什么是原型
一句話說明什么是原型:原型能存儲我們的方法,構(gòu)造函數(shù)創(chuàng)建出來的實例對象能夠引用原型中的方法。
JS中一切皆對象,而每個對象都有一個原型(Object除外),這個原型,大概就像Java中的父類,所以,基本上你可以認為原型就是這個對象的父對象,即每一個對象(Object除外)內(nèi)部都保存了它自己的父對象,這個父對象就是原型。一般創(chuàng)建的對象如果沒有特別指定原型,那么它的原型就是Object(這就很類似Java中所有的類默認繼承自O(shè)bject類)。
ES6通過引入class ,extends等關(guān)鍵字,以一種語法糖的形式把構(gòu)造函數(shù)包裝成類的概念,更便于大家理解。是希望開發(fā)者不再花精力去關(guān)注原型以及原型鏈,也充分說明原型的設(shè)計意圖和類是一樣的。
5.2 查看對象的原型當(dāng)對象被創(chuàng)建之后,查看它們的原型的方法不止一種,以前一般使用對象的__proto__屬性,ES6推出后,推薦用Object.getPrototypeOf()方法來獲取對象的原型
function A(){ this.name="lala"; } var a=new A(); console.log(a.__proto__) //輸出:Object {} //推薦使用這種方式獲取對象的原型 console.log(Object.getPrototypeOf(a)) //輸出:Object {}
無論對象是如何創(chuàng)建的,默認原型都是Object,在這里需要提及的比較特殊的一點就是,通過構(gòu)造函數(shù)來創(chuàng)建對象,函數(shù)A本身也是一個對象,而A有兩個指向表示原型的屬性,分別是__proto__和prototype,而且兩個屬性并不相同
function A(){ this.name="lala"; } var a=new A(); console.log(A.prototype) //輸出:Object {} console.log(A.__proto__) //輸出:function () {} console.log(Object.getPrototypeOf(A)) //輸出:function () {}
函數(shù)的的prototype屬性只有在當(dāng)作構(gòu)造函數(shù)創(chuàng)建的時候,把自身的prototype屬性值賦給對象的原型。而實際上,作為函數(shù)本身,它的原型應(yīng)該是function對象,然后function對象的原型才是Object。
總之,建議使用ES6推薦的查看原型和設(shè)置原型的方法。
5.3 原型的用法其實原型和類的繼承的用法是一致的:當(dāng)你想用某個對象的屬性時,將當(dāng)前對象的原型指向該對象,你就擁有了該對象的使用權(quán)了。
function A(){ this.name="world "; } function B(){ this.bb="hello" } var a=new A(); var b=new B(); //將b設(shè)置為a的原型,此處有一個問題,即a的constructor也指向了B構(gòu)造函數(shù),可能需要糾正 Object.setPrototypeOf(a,b); a.constructor=A; console.log(a.bb); //hello
如果使用ES6來做的話則簡單許多,甚至不涉及到prototype這個屬性
class B{ constructor(){ this.bb="hello" } } class A extends B{ constructor(){ super(); this.name="world"; } } var a=new A(); console.log(a.bb+" "+a.name); //hello world console.log(typeof(A)) //"function"
怎么樣?是不是已經(jīng)完全看不到原型的影子了?活脫脫就是類繼承,但是你也看得到實際上類A 的類型是function,所以說,本質(zhì)上class在JS中是一種語法糖,JS繼承的本質(zhì)依然是原型,不過,ES6引入class,extends 來掩蓋原型的概念也是一個很友好的舉動,對于長期學(xué)習(xí)那些類繼承為基礎(chǔ)的面對對象編程語言的程序員而言。
我的建議是,盡可能理解原型,盡可能用class這種語法糖。
好了,問自己兩個問題:
為什么要使用原型?——提高函數(shù)的復(fù)用性。
為什么屬性不放在原型上而方法要放在原型上?
利用對象的動態(tài)特性:構(gòu)造函數(shù).prototype.xxxx = vvv
利用直接替換
Student.prototype = { sayHello : function(){}, study : function(){} };5.4 原型鏈
什么是原型鏈?
凡是對象就有原型,那么原型又是對象,因此凡是給定一個對象,那么就可以找到他的原型,原型還有原型,那么如此下去,就構(gòu)成一個對象的序列,稱該結(jié)構(gòu)為原型鏈。
每個實例對象都有一個__proto_屬性,該屬性指向它原型對象,這個實例對象 的構(gòu)造函數(shù)有一個原型屬性 prototype,與實例的__proto__屬性指向同一個對象。當(dāng)一個對象在查找一個屬性的時, 自身沒有就會根據(jù)__proto__ 向它的原型進行查找,如果都沒有,則向它的原型的原型繼續(xù)查找,直到查到 Object.prototype._proto_為 null,這樣也就形成了原型鏈。
這個概念其實也變得比較簡單,可以類比類的繼承鏈條,即每個對象的原型往上追溯,一直到Object為止,這組成了一個鏈條,將其中的對象串聯(lián)起來,當(dāng)查找當(dāng)前對象的屬性時,如果沒找到,就會沿著這個鏈條去查找,一直到Object,如果還沒發(fā)現(xiàn),就會報undefined。
原型鏈的結(jié)構(gòu)
凡是使用構(gòu)造函數(shù),創(chuàng)建出對象,并且沒有利用賦值的方式修改原型,就說該對象保留默認的原型鏈。
默認原型鏈結(jié)構(gòu)是什么樣子呢?
function Person(){} var p = new Person(); //p 具有默認的原型鏈
默認的原型鏈結(jié)構(gòu)就是:當(dāng)前對象 -> 構(gòu)造函數(shù).prototype -> Object.prototype -> null
在實現(xiàn)繼承的時候,有時候會利用替換原型鏈結(jié)構(gòu)的方式實現(xiàn)原型繼承,那么原型鏈結(jié)構(gòu)就會發(fā)生改變
function DunizbCollection(){} DunizbCollection.prototype = []; var arr = new DunizbCollection();
此時arr對象的原型鏈結(jié)構(gòu)被指向了數(shù)組對象的原型鏈結(jié)構(gòu)了:arr -> [] -> Array.prototype -> Object.prototype -> null
用圖形表示對象的原型鏈結(jié)構(gòu)
以如下代碼為例繪制原型鏈結(jié)構(gòu)
function Person(){} var p = new Person();
原型鏈結(jié)構(gòu)圖為:
使用原型需要注意兩點:
原型繼承鏈條不要太長,否則會出現(xiàn)效率問題。
指定原型時,注意constructor也會改變。
六、繼承實現(xiàn)繼承有兩種常見方式:
6.1 混合式繼承最簡單的繼承就是將別的對象的屬性強加到我身上,那么我就有這個成員了。
混合式繼承的簡單描述:
function Person() {}; Person.prototype.extend = function ( o ) { for ( var k in o ) { this[ k ] = o[ k ]; } }; Person.prototype.extend({ run: function () { console.log( "我能跑了" ); }, eat: function () { console.log( "我可以吃了" ); }, sayHello: function () { console.log( "我吃飽了" ); } });6.2 原型繼承
利用原型也可以實現(xiàn)繼承,不需要在我身上添加任何成員,只要原型有了我就有了。
6.3 借用構(gòu)造函數(shù)繼承這種技術(shù)的基本思想相當(dāng)簡單,即在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù),而函數(shù)只不過是在特定環(huán)境中執(zhí)行代碼的對象,因此通過使用apply()和call()方法也可以在(將來)新創(chuàng)建的對象上執(zhí)行構(gòu)造函數(shù)
function Person ( name, age, gender ) { this.name = name; this.age = age; this.gender = gender; } // 需要提供一個 Student 的構(gòu)造函數(shù)創(chuàng)建學(xué)生對象 // 學(xué)生也應(yīng)該有 name, age, gender, 同時還需要有 course 課程 function Student ( name, age, gender, course ) { Person.call( this, name, age, gender ); this.course = course; }
在《JavaScript高級程序設(shè)計(第三版)》中詳細介紹了繼承的6種方式
七、函數(shù)的四種調(diào)用模式 7.1 函數(shù)模式就是一個簡單的函數(shù)調(diào)用。函數(shù)名的前面沒有任何引導(dǎo)內(nèi)容。
function foo () {} var func = function () {}; ... foo(); func(); (function () {} )();
this 的含義:在函數(shù)中 this 表示全局對象,在瀏覽器中式 window
7.2 方法模式方法一定式依附與一個對象,將函數(shù)賦值給對象的一個屬性,那么就成為了方法。
function f() { this.method = function () {}; } var o = { method: function () {} }
this 的含義:這個依附的對象
7.3 構(gòu)造器調(diào)用模式創(chuàng)建對象的時候構(gòu)造函數(shù)做了什么?由于構(gòu)造函數(shù)只是給 this 添加成員,沒有做其他事情。而方法也可以完成這個操作,就是 this 而言,構(gòu)造函數(shù)與方法沒有本質(zhì)的區(qū)別。
特征:
使用 new 關(guān)鍵字,來引導(dǎo)構(gòu)造函數(shù)。
構(gòu)造函數(shù)中的 this 與方法中的一樣,表示對象,但是構(gòu)造函數(shù)中的對象是剛剛創(chuàng)建出來的對象
構(gòu)造函數(shù)中不需要 return ,就會默認的 return this。
如果手動添加return ,就相當(dāng)于 return this
如果手動的添加 return 基本類型,無效,還是保留原來 返回 this
如果手動添加的 return null,或 return undefined ,無效
如果手動添加 return 對象類型,那么原來創(chuàng)建的 this 就會被丟掉,返回的是 return 后面的對象
7.4 上下文調(diào)用模式上下文就是環(huán)境。就是自己定義設(shè)置 this 的含義。
語法
函數(shù)名.apply( 對象, [ 參數(shù) ] );
函數(shù)名.call( 對象, 參數(shù) );
描述
函數(shù)名就是表示函數(shù)本身,使用函數(shù)進行調(diào)用的時候默認 this 是全局變量
函數(shù)名也可以是方法提供,使用方法調(diào)用的時候,this 是指向當(dāng)前對象
使用 apply 進行調(diào)用后,無論是函數(shù)還是方法都無效了,我們的 this ,由 apply 的第一個參數(shù)決定
參數(shù)問題
無論是 call 還是 apply 在沒有后面的參數(shù)的情況下(函數(shù)無參數(shù),方法五參數(shù))是完全一致的
function foo(){ console.log( this ); } foo.apply( obj ); foo.call( obj );
第一個參數(shù)的使用也是有規(guī)則的:
如果傳入的是一個對象,那么就相當(dāng)于設(shè)置該函數(shù)中的 this 為參數(shù)
如果不傳入?yún)?shù),或傳入 null、undefined 等,那么相當(dāng)于 this 默認為 window
foo(); foo.apply(); foo.apply( null );
如果傳入的是基本類型,那么 this 就是基本類型對應(yīng)的包裝類型的引用
在使用上下文調(diào)用的時候,原函數(shù)(方法)可能會帶有參數(shù),那么這個參數(shù)再上下文調(diào)用中使用 第二個(第 n 個)參數(shù)來表示
function foo( num ) { console.log( num ); } foo.apply( null, [ 123 ] ); // 等價于 foo( 123 );
參考資料
本文原型部分部分引用自《JavaScript原型詳解》,版權(quán)歸原作者所有
js閉包的用途
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/91083.html
摘要:中很多特性或者說知識點都是和面向?qū)ο缶幊谈拍钕嚓P(guān)的。在多線程中內(nèi)容有很多,只是簡單說明一下中初步使用多線程需要掌握的知識點,以后有機會單獨再詳細介紹一些高級特性的使用場景。 寫這篇文章的目的是想總結(jié)一下自己這么多年來使用java的一些心得體會,主要是和一些java基礎(chǔ)知識點相關(guān)的,所以也希望能分享給剛剛?cè)腴T的Java程序員和打算入Java開發(fā)這個行當(dāng)?shù)臏?zhǔn)新手們,希望可以給大家一些經(jīng)...
摘要:在他的重學(xué)前端課程中提到到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系中的重要崗位之一。大部分前端工程師的知識,其實都是來自于實踐和工作中零散的學(xué)習(xí)。一基礎(chǔ)前端工程師吃飯的家伙,深度廣度一樣都不能差。 開篇 前端開發(fā)是一個非常特殊的行業(yè),它的歷史實際上不是很長,但是知識之繁雜,技術(shù)迭代速度之快是其他技術(shù)所不能比擬的。 winter在他的《重學(xué)前端》課程中提到: 到現(xiàn)在為止,前端工程師已經(jīng)成為研...
摘要:在他的重學(xué)前端課程中提到到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系中的重要崗位之一。大部分前端工程師的知識,其實都是來自于實踐和工作中零散的學(xué)習(xí)。一基礎(chǔ)前端工程師吃飯的家伙,深度廣度一樣都不能差。開篇 前端開發(fā)是一個非常特殊的行業(yè),它的歷史實際上不是很長,但是知識之繁雜,技術(shù)迭代速度之快是其他技術(shù)所不能比擬的。 winter在他的《重學(xué)前端》課程中提到: 到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系...
摘要:如果沒有學(xué)習(xí)過計算機科學(xué)的程序員,當(dāng)我們在處理一些問題時,比較熟悉的數(shù)據(jù)結(jié)構(gòu)就是數(shù)組,數(shù)組無疑是一個很好的選擇。 showImg(https://segmentfault.com/img/bVTSjt?w=400&h=300); 1、常見 CSS 布局方式詳見: 一些常見的 CSS 布局方式梳理,涉及 Flex 布局、Grid 布局、圣杯布局、雙飛翼布局等。http://cherryb...
閱讀 3354·2021-11-10 11:36
閱讀 3250·2021-10-08 10:21
閱讀 2883·2021-09-29 09:35
閱讀 2428·2021-09-22 16:06
閱讀 3983·2021-09-09 09:33
閱讀 1337·2019-08-30 15:44
閱讀 3180·2019-08-30 10:59
閱讀 2992·2019-08-29 15:32