您好,登錄后才能下訂單哦!
這篇文章主要講解了JavaScript面向?qū)ο笫鞘裁?,?nèi)容清晰明了,對此有興趣的小伙伴可以學(xué)習(xí)一下,相信大家閱讀完之后會有幫助。
一、面向?qū)ο?/span>
抽象性
所謂的抽象性就是:如果需要一個對象描述數(shù)據(jù),需要抽取這個對象的核心數(shù)據(jù)
封裝性
對象是將數(shù)據(jù)與功能組合到一起,即封裝
繼承性
所謂繼承性就是自己沒有但是別人有,拿過來成為自己的,就是繼承,繼承是實現(xiàn)復(fù)用的一種手段
在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 ]; } }
類class:在JS中就是構(gòu)造函數(shù)
實例(instance)與對象(object)
鍵值對與屬性和方法
父類與子類(基類和派生類)
var p = new Person();
首先運算符new
創(chuàng)建了一個對象,類似于{}
,是一個沒有任何(自定義)成員的對象。
new
創(chuàng)建對象,那么對象的類型就是創(chuàng)建他的構(gòu)造函數(shù)名{}
無論如何都是Object類型,相當于new Object
然后調(diào)用構(gòu)造函數(shù),為其初始化成員
域表示的就是范圍,即作用域,就是一個名字在什么地方可以使用,什么時候不能使用。
簡單的說,作用域是針對變量的,比如我們創(chuàng)建一個函數(shù) a1
,函數(shù)里面又包了一個子函數(shù) a2
。
// 全局作用域 functiona a1() { // a1作用域 function a2() { // a2作用域 } }
此時就存 在三個作用域:全局作用域,a1
作用域,a2
作用域;即全局作用域包含了 a1
的作用域,a2
的作用域包含了 a1
的作用域。
當 a2
在查找變量的時候會先從自身的作用域區(qū)查找,找不到再到上一級 a1
的作用域查找,如果還沒找到就
到全局作用域區(qū)查找,這樣就形成了一個作用域鏈
。
實用閉包主要是為了設(shè)計私有方法和變量。閉包的優(yōu)點是可以避免全局變量的污染;缺點是閉包會常駐內(nèi)存,增加內(nèi)存使用量,使用不當很容易造成內(nèi)存泄露。在JavaScript中,函數(shù)即閉包,只有函數(shù)才能產(chǎn)生作用域。
閉包有3個特性:
閉包的作用,就是保存自己私有的變量,通過提供的接口(方法)給外部使用,但外部不能直接訪問該變量。
通過使用閉包,我們可以做很多事情,比如模擬面向?qū)ο蟮拇a風格;更優(yōu)雅,更簡潔的表達出代碼;在某些方面提升代碼的執(zhí)行效率。利用閉包可以實現(xiàn)如下需求:
一個匿名的函數(shù),并立即執(zhí)行它,由于外部無法引用它內(nèi)部的變量,因此在執(zhí)行完后很快就會被釋放,關(guān)鍵是這種機制不會污染全局對象。
閉包正是可以做到這一點,因為它不會釋放外部的引用,從而函數(shù)內(nèi)部的值可以得以保留。
對象模式
函數(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( ... )
js 垃圾回收機制,也就是當一個函數(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;
一句話說明什么是原型:原型能存儲我們的方法,構(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è)計意圖和類是一樣的。
當對象被創(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
屬性只有在當作構(gòu)造函數(shù)創(chuàng)建的時候,把自身的prototype
屬性值賦給對象的原型。而實際上,作為函數(shù)本身,它的原型應(yīng)該是function
對象,然后function
對象的原型才是Object
。
總之,建議使用ES6推薦的查看原型和設(shè)置原型的方法。
其實原型和類的繼承的用法是一致的:當你想用某個對象的屬性時,將當前對象的原型指向該對象,你就擁有了該對象的使用權(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
這種語法糖。
好了,問自己兩個問題:
為什么屬性不放在原型上而方法要放在原型上?
構(gòu)造函數(shù).prototype.xxxx = vvv
Student.prototype = { sayHello : function(){}, study : function(){} };
什么是原型鏈?
凡是對象就有原型,那么原型又是對象,因此凡是給定一個對象,那么就可以找到他的原型,原型還有原型,那么如此下去,就構(gòu)成一個對象的序列,稱該結(jié)構(gòu)為原型鏈。
每個實例對象都有一個__proto_
屬性,該屬性指向它原型對象,這個實例對象 的構(gòu)造函數(shù)有一個原型屬性prototype
,與實例的__proto__
屬性指向同一個對象。當一個對象在查找一個屬性的時, 自身沒有就會根據(jù)__proto__
向它的原型進行查找,如果都沒有,則向它的原型的原型繼續(xù)查找,直到查到Object.prototype._proto_
為null
,這樣也就形成了原型鏈
。
這個概念其實也變得比較簡單,可以類比類的繼承鏈條,即每個對象的原型往上追溯,一直到Object為止,這組成了一個鏈條,將其中的對象串聯(lián)起來,當查找當前對象的屬性時,如果沒找到,就會沿著這個鏈條去查找,一直到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)就是:當前對象 -> 構(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)圖為:
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( '我吃飽了' ); } });
利用原型也可以實現(xiàn)繼承,不需要在我身上添加任何成員,只要原型有了我就有了。
這種技術(shù)的基本思想相當簡單,即在子類型構(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)用。函數(shù)名的前面沒有任何引導(dǎo)內(nèi)容。
function foo () {} var func = function () {}; ... foo(); func(); (function () {} )();
this 的含義:在函數(shù)中 this
表示全局對象,在瀏覽器中式 window
方法一定式依附與一個對象,將函數(shù)賦值給對象的一個屬性,那么就成為了方法。
function f() { this.method = function () {}; } var o = { method: function () {} }
this
的含義:這個依附的對象
創(chuàng)建對象的時候構(gòu)造函數(shù)做了什么?由于構(gòu)造函數(shù)只是給 this
添加成員,沒有做其他事情。而方法也可以完成這個操作,就是 this
而言,構(gòu)造函數(shù)與方法沒有本質(zhì)的區(qū)別。
特征:
new
關(guān)鍵字,來引導(dǎo)構(gòu)造函數(shù)。this
與方法中的一樣,表示對象,但是構(gòu)造函數(shù)中的對象是剛剛創(chuàng)建出來的對象構(gòu)造函數(shù)中不需要 return
,就會默認的 return this
。
return
,就相當于 return this
return
基本類型,無效,還是保留原來 返回 this
return null
,或 return undefined
,無效return
對象類型,那么原來創(chuàng)建的 this
就會被丟掉,返回的是 return
后面的對象上下文就是環(huán)境。就是自己定義設(shè)置 this 的含義。
語法
函數(shù)名.apply( 對象, [ 參數(shù) ] )
;函數(shù)名.call( 對象, 參數(shù) )
;描述
參數(shù)問題
無論是 call 還是 apply 在沒有后面的參數(shù)的情況下(函數(shù)無參數(shù),方法五參數(shù))是完全一致的
function foo(){ console.log( this ); } foo.apply( obj ); foo.call( obj );
第一個參數(shù)的使用也是有規(guī)則的:
null
、undefined
等,那么相當于 this
默認為 window
foo(); foo.apply(); foo.apply( null );
在使用上下文調(diào)用的時候,原函數(shù)(方法)可能會帶有參數(shù),那么這個參數(shù)再上下文調(diào)用中使用 第二個(第 n 個)參數(shù)來表示
function foo( num ) { console.log( num ); } foo.apply( null, [ 123 ] ); // 等價于 foo( 123 );
看完上述內(nèi)容,是不是對JavaScript面向?qū)ο笫鞘裁从羞M一步的了解,如果還想學(xué)習(xí)更多內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。