javascript 高級特性探討A4-A5(call和原型.對象復制)

? 在js中,call和apply是二個神奇的方法,但同時也是容易令人迷惑的二個方法,call和apply的功能是以不同的對象作為上下文來調用某個函數的,簡而言之,就是允許一個對象去調用另一個對象的成員函數,咋一看似乎很不可思議,而且還容易引起混亂,但其實js并沒有嚴格的所謂’成員函數‘的概念,函數與對象的所屬關系在調用時才展現出來,靈活使用call和apply可以節省不少時間,在后面我們可以看到,call可以用于實現對象的繼承

? call和apply的功能是一致的,二者細微的差別在于call以參數表來接受被調用函數的參數,而apply以數組來接受被調用函數的參數,call和apply的語法分別是:

? ??

func.call(thisArg[, arg1[, arg2[, ... ]]])
func.apply(thisArg[, argsArray] )

? 其中,func是函數的引用,thisArg是func被調用時的山修改文對象,arg1,arg2或argsArray是傳入func的參數,我們以下面一段代碼為例介紹call的工作機制:

 1 var someuser = {
 2     name: 'by',
 3     display: function (words) {
 4         console.log(this.name + ' says ' + words);
 5     }
 6 };
 7 var foo = {
 8     name: 'foo'
 9 };
10 someuser.display.call(foo, 'hello'); //輸出foo says hello

someuser.display是被調用的函數,它通過call將上下文改變為foo對象,因此在函數體內訪問的this.name時實際上訪問的就是foo.name,因此輸出的即是foo.

2bind

如何改變被調用函數的上下文呢?前面說過,可以用call或apply方法,但如果重復使用不方便,因此每次都要把上下文對象作為參數傳遞,而且還會使代碼變得不直觀,針對這種情況,我們可以使用bind方法來永久的綁定函數的上下文,使其無論被誰調用,上下文都是固定的,bind語法如下:

func.bind(thisArg[, arg1[, arg2[, ...]]])

?其中func是待綁定函數,thisArg是改變的上下文對象,arg1,arg2是綁定的參數表,bind方法返回值是上下文為thisArg的func,通過下面這個例子可以幫我們理解bind的使用方法:

 1 var someuser = {
 2     name: 'by',
 3     func: function () {
 4         console.log(this.name);
 5     }
 6 };
 7 var foo = {
 8     name: 'foo'
 9 };
10 foo.func = someuser.func;
11 foo.func(); //輸出foo
12 
13 foo.func1 = someuser.func.bind(someuser);
14 foo.func1(); //輸出by
15 
16 func = someuser.func.bind(foo);
17 func(); //輸出foo
18 
19 func2 = func;
20 func2(); //輸出foo

?上面代碼直接將foo.func 賦值給someuser.func,調用foo.func()時, this指針為foo,所以輸出結果是foo.

foo.func1使用了bind方法,將someuser作為this指針綁定到someuser.func,調用foo.func1()時,this指針為someuser,所以輸出結果是by.

全局函數func同樣使用了bind方法,將foo作為this指針綁定到someuser.func,調用func()時,this指針為foo,所以輸出結果是foo,而func2直接將綁定過的func賦值過來,與func行為完全相同

3使用 bind綁定參數表

bind方法還有一個重要功能,綁定參數表,如下例所示:

 1 var person = {
 2     name: 'by',
 3     says: function (act, obj) {
 4         console.log(this.name + ' ' + act + ' ' + obj);
 5     }
 6 };
 7 person.says('loves', 'diovyb'); //輸出by loves diovyb
 8 
 9 bya = person.says.bind(person, 'loves');
10 bya('you'); //輸出by loves you

? 可以看到,bya將this指針綁定到了person,并將第一個參數綁定到loves,之后在調用bya的時候,只需傳入第三個參數,這個特性可以用于創建一個函數的’捷徑‘,之后我們可以通過這個捷徑調用,以便在代碼多處調用時省略重復輸入相同的參數

4理解bind

盡管bind很優美,還是有一些令人疑惑的地方,例如下面的代碼:

 1 var someuser = {
 2      name: 'by',
 3     func: function () {
 4         console.log(this.name);
 5     }
 6 };
 7 var foo = {
 8     name : 'foo'
 9 };
10 func = someuser.func.bind(foo);
11 func(); //輸出foo
12 
13 func2 = func.bind(someuser);
14 func2(); //輸出foo

全局函數func通過someuser.func.bind將this指針綁定到了foo,調用func()輸出了foo,我們試圖將func2賦值為已綁定的func重新通過bind將this指針綁定到someuser的結果,而調用func2時的輸出卻竟然沒有像我們想像中的那樣變成by,這是為森么呢,繼續看下去,讓我為你揭秘

Bind方法的簡化版(不支持綁定參數表)

1 someuser.func.bind = function (self) {
2     return this.call(self);
3 };

? ?假設上面函數是someuser.func的bind方法的實現,函數體內的this指向的是someuser.func,因此函數也是對象,所以this.call(self)的作用就是以self作為this指針調用someuser.func

1 //將func = someuser.func.bind(foo) 展開
2 func = function () {
3     return someuser.func.call(foo);
4 };
5 //再將func2 = func.bind(someuser) 展開
6 func2 = function () {
7     return func.call(someuser);
8 };

從上面的展開過程我們可以看出,func2實際上是以 someuser 作為func的this 指針調用了func,而func根本沒有使用this指針,所以二次bind是沒有效果的。

A5原型

原型是js面向對象中重要特性

 1 function person() {
 2 
 3 }
 4 person.prototype.name = 'by';
 5 person.prototype.showName = function () {
 6     console.log(this.name);
 7 };
 8 
 9 var person = new person();
10 person.showName();

上面這段代碼使用了原型而不是構造函數初始化對象,這樣做與直接在構造函數中定義屬性有什么不同呢?

1構造函數內定義的屬性繼承方式與原型不同,子對象需要顯示調用父對象才能繼承構造函數內部定義的屬性

2.構造函數內定義的任何屬性,包括函數在內都會被重復創建,同一個構造函數產生的二個對象不共享實例

3構造函數內定義的函數有運行時閉包的開銷,因為構造函數內的局部變量對其中定義的函數來說是可見的

下面的這段代碼可以驗證以上問題:

 1 function foo() {
 2     var inner = 'hello';
 3     this.prop1 = 'by';
 4     this.func1 = function () {
 5         inner = '';
 6     };
 7 }
 8 foo.prototype.prop2 = 'car';
 9 foo.prototype.func2 = function () {
10     console.log(this.prop2);
11 };
12 var foo1 = new foo();
13 var foo2 = new foo();
14 console.log(foo1.func1 == foo2.func1); //輸出false
15 console.log(foo1.func2 == foo2.func2); //輸出 true

盡管如此,并不是說在構造函數內創建屬性不好,而是二者各自有適合的范圍,那么我們什么時候使用原型,什么時候使用構造函數來定義內部屬性

1除非必須用構造函數閉包,否則盡量用原型定義成員函數,因為這樣可以減少開銷

2.盡量在構造函數內定義一般成員,尤其是對象或數組,因為用原型定義的成員是多個實例共享的

接下來,我們來介紹js的原型鏈機制

? ?js中有二個特殊的對象:object與function,它們都是構造函數,用于生成對象,object.prototype是所有對象的祖先,function.prototype是所有函數的原型,包括構造函數,我把js中的對象分為三類:一類用戶創建的對象,一類是構造函數對象,一類是原型對象。用戶創建的對象,即一般意義上用new語句顯示構造的對象,構造函數對象指的是普通的構造函數,即通過new調用生成普通對象的函數,原型對象特指構造函數prototype屬性指向的對象,這三類對象中的每一類都有一個__p[roto__屬性,它指向該對象的原型,從任何對象沿著它開始遍歷都可以追溯到object.prototype,構造函數對象有prototype屬性,指向一個原型對象,通過該構造函數創建對象時,被創建對象的__proto__屬性將會指向構造函數的prototype屬性,原型對象有constructor屬性,指向它對應的構造函數,讓我們通過下面的例子來理解原型

 1 function foo() {
 2     
 3 }
 4 object.prototype.name = 'my';
 5 foo.prototype.name = 'bar';
 6 
 7 var obj = new object();
 8 var foo = new foo();
 9 console.log(obj.name); //輸出my
10 console.log(foo.name); //輸出bar
11 console.log(foo.__proto__.name); //輸出bar
12 console.log(foo.__proto__.__proto__.name); //輸出my
13 console.log(foo.__proto__.constructor.prototype.name); //輸出bar

我們定義了一個叫做foo()的構造函數,生成對象foo.同時我們還分別給object和foo生成原型對象.

? ? 在js中,繼承是以依靠一套叫做原型鏈的機制實現的,屬性繼承的本質就是一個對象可以訪問到它的原型鏈上任何一個原型對象的屬性,例如上例的foo對象,它擁有foo.__Proto__和foo.__proto__.proto__所有屬性的淺拷貝(只復制基本數據類型,不復制對象),所以可以直接訪問foo.constructor(來自foo.__proto__,即foo.prototype), ?foo.tostring(來自foo.__proto__.__proto__,即object.prototype).

A6對象的復制

js中沒有像c語言一樣的指針,也沒有像java一樣的clone方法可以進行對象賦值,因此我們需要手動實現這樣一個函數,一個簡單的做法就是復制對象的所有屬性:

 1 object.prototype.clone = function () {
 2     var newobj = {};
 3     for (var i in this){
 4         newobj[i] = this[i];
 5     }
 6     return newobj;
 7 };
 8 var obj = {
 9     name: 'by',
10     likes:  ['node']
11 };
12 var newobj = obj.clone();
13 obj.likes.push('python');
14 
15 console.log(obj.likes); //輸出['node','python']
16 console.log(newobj.likes); //輸出['node','python']

?上面的代碼是一個對象淺拷貝的實現,即只復制基本類型的屬性,而共享對象類型的屬性,淺拷貝的問題是二個對象共享對象類型的屬性,例如上例中的likes屬性指向的是同一個數組

? 實現一個完全的復制,或深拷貝不是一件容易的事,因為除了基本數據類型,還有多種不同的對象,對象內部還有復雜的結構,因此需要用遞歸來實現

 1 object.prototype.clone = function () {
 2     var newobj = {};
 3     for (var i in this){
 4         if (typeof(this[i]) == 'object' || typeof(this[i] == 'function')){
 5             newobj[i] = this[i].clone();
 6         } else {
 7             newobj[i] = this[i];
 8         }
 9     }
10     return newobj;
11 };
12 Array.prototype.clone = function () {
13     var newArray = [];
14     for (var i = 0; i< this.length;i++){
15         if (typeof(this[i]) == 'object' || typeof(this[i] == 'function')){
16            newArray[i] = this[i].clone();
17         } else {
18             newArray[i] = this[i];
19         } 
20     }
21     return newArray;
22 };
23 function.prototype.clone = function () {
24     var that = this;
25     var newfunc = function(){
26         return that.apply(this, arguments);
27     };
28     for (var i in this){
29         newfunc[i] = this[i];
30     }
31     return newfunc();
32 };
33 var obj = {
34     name: 'by',
35     likes: ['node'],
36     display: function(){
37         console.log(this.name);
38     }
39 };
40 var newobj = obj.clone();
41 newobj.likes.push('python');
42 console.log(obj.likes); //輸出['node']
43 console.log(newobj.likes); //輸出['node','python']
44 console.log(obj.display == newobj.display); //輸出false

上面這個辦法實現雖然看上去非常完美,它不僅遞歸的復制了對象復雜的結構,還實現了函數的深拷貝,這個方法在大多數的情況都好用,但是有一種情況它無能為力,例如下面的代碼

var obj1 = {ref: null
};
var obj2 = {ref: obj1
};
obj1.ref = obj2;

這段代碼邏輯非常簡單,就是二個相互引用的對象,當我們試圖使用深拷貝來復制obj1和obj2中的任何一個時,問題就出現了,因為深拷貝的做法就是遇到對象就進行遞歸復制,那么結果只能無限循環下去,對于這種情況,簡單的遞歸已經無法解決,必須設計一套圖論算法,分析對象之間的依賴關系,建立一個拓撲結構圖,然后分別依次復制每個頂點,并重新構建它們之間的依賴關系,這個我們暫且不討論了,過于高深,而且在實際中我們也幾乎不會遇到這種情況,好的,js全部重要的就在這里了,讓我們下次再見!!!

?

轉載于:https://www.cnblogs.com/237325670qqcom/p/5709095.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/374291.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/374291.shtml
英文地址,請注明出處:http://en.pswp.cn/news/374291.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

帶有NetBeans 7.1 RC 2的WebLogic 12c快速入門

WebLogic服務器12c停運了幾天。 它是針對“裸露”的Java開發人員的–花哨的Fusion Middleware東西將繼續沿線升至12c。 因此&#xff0c;這基本上是我要運行的版本。 今天&#xff0c;我為您提供了一個最新的NetBeans 7.1&#xff08;RC 2&#xff09;和WebLogic的快速入門 &am…

C學習雜記(四)sizeof計算聯合體大小

#include <stdio.h>union u1 {char a[13];int b; };int main(void) {printf("%d\n", sizeof(u1));return 0; } 結果為16。 聯合體的大小取決于它所有的成員中占用空間最大的一個成員的大小。u2最大的空間是char[13]&#xff0c;但是因為另一個成員int b的存在…

python爬蟲反爬機制_Python Scrapy突破反爬蟲機制(項目實踐)

對于 BOSS 直聘這種網站&#xff0c;當程序請求網頁后&#xff0c;服務器響應內容包含了整個頁面的 HTML 源代碼&#xff0c;這樣就可以使用爬蟲來爬取數據。但有些網站做了一些“反爬蟲”處理&#xff0c;其網頁內容不是靜態的&#xff0c;而是使用 JavaScript 動態加載的&…

樹的算法 已知二叉樹的前序序列和中序序列求解樹

題目: 已知二叉樹的前序序列和中序序列求解樹 比如 6 4    8 3  5   7 前序序列為6,4,3,5,8,7 中序序列為3,4,5,6,7,8 思路: 前序遍歷序列的第一個元素必為根節點 則中序遍歷序列中&#xff0c;該節點之前的為左子樹&#xff0c;該節點之后的為右子樹&#xff0c;若該節…

使用Spring配置LogBack日志記錄

LogBack是由Log4j的同一作者創建的用于記錄日志的API&#xff08;較新的實現&#xff0c;它類似于新版本&#xff09;&#xff0c;在本文中&#xff0c;我將展示如何在Spring項目中對其進行集成和使用。 在本教程中&#xff0c;我假設您正在使用一個簡單的Spring ROO項目&…

自定義URL Scheme完全指南

iPhone / iOS SDK 最酷的特性之一就是應用將其自身”綁定”到一個自定義 URL scheme 上&#xff0c;該 scheme 用于從瀏覽器或其他應用中啟動本應用。 注冊自定義 URL Scheme 注冊自定義 URL Scheme 的第一步是創建 URL Scheme — 在 Xcode Project Navigator 中找到并點擊工程…

C學習雜記(五)形參實參筆試題

大意失荊州 不要以為簡單就輕視&#xff0c;謹慎&#xff0c;細節&#xff0c;基礎。 一、有以下程序 #include <stdio.h>typedef struct {int b, p;} A;void f(A c) {c.b 1; c.p 2; }void main(void) {A a {1, 2};f(a);printf("%d, %d\n", a.b, a.p); } …

avframe轉byte數組_C# amp; VB6.0 圖像與二維數組 互轉

背景最近在研究C#進行圖像處理&#xff0c;在圖像處理中算法中&#xff0c;往往都是針對的是矩陣運算的。矩陣其實就是一個二維的數組。為了圖像處理的速度&#xff0c;我們都需要放在內存中處理。但網絡上收集的代碼&#xff0c;往往都是以一維數組的樣子提供結果&#xff0c;…

C學習雜記(六)%2.0f打印輸出寬度

%m.nf&#xff0c;m表示整個浮點數的輸出寬度&#xff0c;n表示小數輸出寬度。 1、printf("%f\n", 12.34); 輸出為12.340000。 2、printf("%2.0f\n", 12.34); 輸出為12。 3、printf("%2.1f\n", 12.34); 輸出為12.3。 4、printf(&qu…

P6 音頻格式—— AAC

目錄 前言 01 AAC是什么&#xff1f; 02 為什么需要進行AAC進行音頻壓縮處理&#xff1f; 03 AAC的特點以及優勢 04 AAC格式詳解&#xff1a; 4.1. ADIF的數據結構&#xff1a; 4.1.1 ADIF Header具體的表格: 4.2. ADTS的結構&#xff08;重點&#xff09;&#xff1a; …

Android開發筆記——ListView模塊、緩存及性能

ListView是Android開發中最常用的組件之一。本文將重點說明如何正確使用ListView&#xff0c;以及使用過程中可能遇到的問題。 ListView開發模塊圖片緩存可能遇到的問題一、ListView開發模塊 從項目實踐的角度來看&#xff0c;ListView適合“自底向上”的開發模式&#xff0c;即…

python實現excel篩選功能并輸出_python如何實現excel按顏色篩選功能

離島 2020-07-09 09:37 已采納 不太了解具體需求&#xff0c;提供一些示例代碼和思路供你參考&#xff1a; 整體思路&#xff1a;首先已知excel中的顏色值&#xff0c;根據編碼實現顏色篩選的功能 示例&#xff1a; 1、首先安裝pip install openpyxl 2、示例代碼可以獲取Excel中…

什么是CDI,它與@EJB和Spring有什么關系?

簡要概述了Java EE中的依賴項注入&#xff0c; Resource / EJB和Inject之間的區別以及它們與Spring的關系-主要是鏈接形式。 上下文依賴注入&#xff08;CDI&#xff0c; JSR 299 &#xff09;是Java EE 6 Web Profile的一部分&#xff0c;它本身基于Java依賴注入&#xff08;…

C學習雜記(七)extern聲明可省略變量類型

工作三年&#xff0c;看C的書也不少。第一次知道extern可以省略變量類型。 b.c有一個全局變量unsigned int data_length&#xff0c;a.c想要調用它&#xff0c;通常使用: extern unsigned int data_length&#xff1b; 在聲明時可以把外部變量類型去掉&#xff1a;extern da…

KMP模板

1 ///KMP模板2 ///生成next數組3 void get_next()4 {5 int i0,j-1;6 next[0]-1;7 while (s1[i])8 {9 if (j-1||s1[i]s1[j]) 10 { 11 i; 12 j; 13 next[i]j; 14 } 15 else jnext[j]; 16 …

使用Apache CXF進行Web服務學習

在我的最后幾個項目中&#xff0c;我使用了Web服務&#xff0c;在某些地方創建它們并在其他地方使用它們。 我覺得創建客戶端&#xff0c;創建Web服務等標準任務非常簡單&#xff0c;如果遇到問題&#xff0c;有足夠的資源。 但是對于Web服務&#xff0c;這是一項瑣碎的任務&am…

python的easygui_Python的easygui學習

1.調用方法 &#xff08;1&#xff09;import easygui easygui.msgbox(…) &#xff08;2&#xff09;from easygui import msgbox(…) 2.函數方法 import easygui a easygui.msgbox(’…’, title‘title’) # show a:返回ok,none b easygui.enterbox( ‘plaese give a solu…

c#遞歸

一種算法&#xff0c;通過簡潔的語句定義無限集合、函數或者子程序在運行時直接或間接調用自身產生重入的現象。 特點&#xff1a;遞歸算法分遞推&#xff08;簡單到復雜的推理過程&#xff09;和回歸&#xff08;獲得簡單解后逐級返回得到復雜的解&#xff09;2個階段。 可理解…

HDU5724

題意&#xff1a; 一個 n * 20 的棋盤&#xff0c;棋盤上有若干棋子&#xff0c;Alice 和 Bob 輪流走&#xff0c;每人每次可以選擇任一行的一顆棋子向右移動到最近的一個空格 &#xff1b;也就是說如果右邊與它相鄰的格子里沒有棋子&#xff0c;就移到右邊與他相鄰的格子去&am…

C語言代碼規范(九)運算符優先級使用括號提高閱讀性

舉簡單例子 a b | c << d 2; 對于大牛沒有問題&#xff0c;對于我這樣的碼農需要思考一下運算優先級 對于這種情況華某有規范使用括號來表示運算順序&#xff0c;從而提高代碼可閱讀性 a b | ( c << (d 2) ); 這樣一目了然&#xff0c;大家好才是真的好。…