成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

【進(jìn)階3-3期】深度解析 call 和 apply 原理、使用場景及實(shí)現(xiàn)

godlong_X / 636人閱讀

摘要:之前文章詳細(xì)介紹了的使用,不了解的查看進(jìn)階期。不同的引擎有不同的限制,核心限制在,有些引擎會拋出異常,有些不拋出異常但丟失多余參數(shù)。存儲的對象能動(dòng)態(tài)增多和減少,并且可以存儲任何值。這邊采用方法來實(shí)現(xiàn),拼成一個(gè)函數(shù)。

之前文章詳細(xì)介紹了 this 的使用,不了解的查看【進(jìn)階3-1期】。

call() 和 apply()
call() 方法調(diào)用一個(gè)函數(shù), 其具有一個(gè)指定的 this 值和分別地提供的參數(shù)(參數(shù)的列表)。

call()apply()的區(qū)別在于,call()方法接受的是若干個(gè)參數(shù)的列表,而apply()方法接受的是一個(gè)包含多個(gè)參數(shù)的數(shù)組

舉個(gè)例子:

var func = function(arg1, arg2) {
     ...
};

func.call(this, arg1, arg2); // 使用 call,參數(shù)列表
func.apply(this, [arg1, arg2]) // 使用 apply,參數(shù)數(shù)組
使用場景

下面列舉一些常用用法:

1、合并兩個(gè)數(shù)組
var vegetables = ["parsnip", "potato"];
var moreVegs = ["celery", "beetroot"];

// 將第二個(gè)數(shù)組融合進(jìn)第一個(gè)數(shù)組
// 相當(dāng)于 vegetables.push("celery", "beetroot");
Array.prototype.push.apply(vegetables, moreVegs);
// 4

vegetables;
// ["parsnip", "potato", "celery", "beetroot"]

當(dāng)?shù)诙€(gè)數(shù)組(如示例中的 moreVegs )太大時(shí)不要使用這個(gè)方法來合并數(shù)組,因?yàn)?strong>一個(gè)函數(shù)能夠接受的參數(shù)個(gè)數(shù)是有限制的。不同的引擎有不同的限制,JS核心限制在 65535,有些引擎會拋出異常,有些不拋出異常但丟失多余參數(shù)。

如何解決呢?方法就是將參數(shù)數(shù)組切塊后循環(huán)傳入目標(biāo)方法

function concatOfArray(arr1, arr2) {
    var QUANTUM = 32768;
    for (var i = 0, len = arr2.length; i < len; i += QUANTUM) {
        Array.prototype.push.apply(
            arr1, 
            arr2.slice(i, Math.min(i + QUANTUM, len) )
        );
    }
    return arr1;
}

// 驗(yàn)證代碼
var arr1 = [-3, -2, -1];
var arr2 = [];
for(var i = 0; i < 1000000; i++) {
    arr2.push(i);
}

Array.prototype.push.apply(arr1, arr2);
// Uncaught RangeError: Maximum call stack size exceeded

concatOfArray(arr1, arr2);
// (1000003)?[-3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]
2、獲取數(shù)組中的最大值和最小值
var numbers = [5, 458 , 120 , -215 ]; 
Math.max.apply(Math, numbers);   //458    
Math.max.call(Math, 5, 458 , 120 , -215); //458

// ES6
Math.max.call(Math, ...numbers); // 458

為什么要這么用呢,因?yàn)閿?shù)組 numbers 本身沒有 max 方法,但是 Math 有呀,所以這里就是借助 call / apply 使用 Math.max 方法。

3、驗(yàn)證是否是數(shù)組
function isArray(obj){ 
    return Object.prototype.toString.call(obj) === "[object Array]";
}
isArray([1, 2, 3]);
// true

// 直接使用 toString()
[1, 2, 3].toString();     // "1,2,3"
"123".toString();         // "123"
123.toString();         // SyntaxError: Invalid or unexpected token
Number(123).toString(); // "123"
Object(123).toString(); // "123"

可以通過toString() 來獲取每個(gè)對象的類型,但是不同對象的 toString()有不同的實(shí)現(xiàn),所以通過 Object.prototype.toString() 來檢測,需要以 call() / apply() 的形式來調(diào)用,傳遞要檢查的對象作為第一個(gè)參數(shù)。

另一個(gè)驗(yàn)證是否是數(shù)組的方法

var toStr = Function.prototype.call.bind(Object.prototype.toString);
function isArray(obj){ 
    return toStr(obj) === "[object Array]";
}
isArray([1, 2, 3]);
// true

// 使用改造后的 toStr
toStr([1, 2, 3]);     // "[object Array]"
toStr("123");         // "[object String]"
toStr(123);         // "[object Number]"
toStr(Object(123)); // "[object Number]"

上面方法首先使用 Function.prototype.call函數(shù)指定一個(gè) this 值,然后 .bind 返回一個(gè)新的函數(shù),始終將 Object.prototype.toString 設(shè)置為傳入?yún)?shù)。其實(shí)等價(jià)于 Object.prototype.toString.call() 。

這里有一個(gè)前提toString()方法沒有被覆蓋

Object.prototype.toString = function() {
    return "";
}
isArray([1, 2, 3]);
// false
4、類數(shù)組對象(Array-like Object)使用數(shù)組方法
var domNodes = document.getElementsByTagName("*");
domNodes.unshift("h1");
// TypeError: domNodes.unshift is not a function

var domNodeArrays = Array.prototype.slice.call(domNodes);
domNodeArrays.unshift("h1"); // 505 不同環(huán)境下數(shù)據(jù)不同
// (505)?["h1", html.gr__hujiang_com, head, meta, ...] 

類數(shù)組對象有下面兩個(gè)特性

1、具有:指向?qū)ο笤氐臄?shù)字索引下標(biāo)和 length 屬性

2、不具有:比如 pushshift、 forEach 以及 indexOf 等數(shù)組對象具有的方法

要說明的是,類數(shù)組對象是一個(gè)對象。JS中存在一種名為類數(shù)組的對象結(jié)構(gòu),比如 arguments 對象,還有DOM API 返回的 NodeList 對象都屬于類數(shù)組對象,類數(shù)組對象不能使用 push/pop/shift/unshift 等數(shù)組方法,通過 Array.prototype.slice.call 轉(zhuǎn)換成真正的數(shù)組,就可以使用 Array下所有方法。

類數(shù)組對象轉(zhuǎn)數(shù)組的其他方法:

// 上面代碼等同于
var arr = [].slice.call(arguments);

ES6:
let arr = Array.from(arguments);
let arr = [...arguments];

Array.from() 可以將兩類對象轉(zhuǎn)為真正的數(shù)組:類數(shù)組對象和可遍歷(iterable)對象(包括ES6新增的數(shù)據(jù)結(jié)構(gòu) Set 和 Map)。

PS擴(kuò)展一:為什么通過 Array.prototype.slice.call() 就可以把類數(shù)組對象轉(zhuǎn)換成數(shù)組?

其實(shí)很簡單,sliceArray-like 對象通過下標(biāo)操作放進(jìn)了新的 Array 里面。

下面代碼是 MDN 關(guān)于 slice 的Polyfill,鏈接 Array.prototype.slice()

Array.prototype.slice = function(begin, end) {
      end = (typeof end !== "undefined") ? end : this.length;

      // For array like object we handle it ourselves.
      var i, cloned = [],
        size, len = this.length;

      // Handle negative value for "begin"
      var start = begin || 0;
      start = (start >= 0) ? start : Math.max(0, len + start);

      // Handle negative value for "end"
      var upTo = (typeof end == "number") ? Math.min(end, len) : len;
      if (end < 0) {
        upTo = len + end;
      }

      // Actual expected size of the slice
      size = upTo - start;

      if (size > 0) {
        cloned = new Array(size);
        if (this.charAt) {
          for (i = 0; i < size; i++) {
            cloned[i] = this.charAt(start + i);
          }
        } else {
          for (i = 0; i < size; i++) {
            cloned[i] = this[start + i];
          }
        }
      }

      return cloned;
    };
  }

PS擴(kuò)展二:通過 Array.prototype.slice.call() 就足夠了嗎?存在什么問題?

低版本IE下不支持通過Array.prototype.slice.call(args)將類數(shù)組對象轉(zhuǎn)換成數(shù)組,因?yàn)榈桶姹綢E(IE < 9)下的DOM對象是以 com 對象的形式實(shí)現(xiàn)的,js對象與 com 對象不能進(jìn)行轉(zhuǎn)換。

兼容寫法如下:

function toArray(nodes){
    try {
        // works in every browser except IE
        return Array.prototype.slice.call(nodes);
    } catch(err) {
        // Fails in IE < 9
        var arr = [],
            length = nodes.length;
        for(var i = 0; i < length; i++){
            // arr.push(nodes[i]); // 兩種都可以
            arr[i] = nodes[i];
        }
        return arr;
    }
}

PS 擴(kuò)展三:為什么要有類數(shù)組對象呢?或者說類數(shù)組對象是為什么解決什么問題才出現(xiàn)的?

JavaScript類型化數(shù)組是一種類似數(shù)組的對象,并提供了一種用于訪問原始二進(jìn)制數(shù)據(jù)的機(jī)制。 Array存儲的對象能動(dòng)態(tài)增多和減少,并且可以存儲任何JavaScript值。JavaScript引擎會做一些內(nèi)部優(yōu)化,以便對數(shù)組的操作可以很快。然而,隨著Web應(yīng)用程序變得越來越強(qiáng)大,尤其一些新增加的功能例如:音頻視頻編輯,訪問WebSockets的原始數(shù)據(jù)等,很明顯有些時(shí)候如果使用JavaScript代碼可以快速方便地通過類型化數(shù)組來操作原始的二進(jìn)制數(shù)據(jù),這將會非常有幫助。

一句話就是,可以更快的操作復(fù)雜數(shù)據(jù)。

5、調(diào)用父構(gòu)造函數(shù)實(shí)現(xiàn)繼承
function  SuperType(){
    this.color=["red", "green", "blue"];
}
function  SubType(){
    // 核心代碼,繼承自SuperType
    SuperType.call(this);
}

var instance1 = new SubType();
instance1.color.push("black");
console.log(instance1.color);
// ["red", "green", "blue", "black"]

var instance2 = new SubType();
console.log(instance2.color);
// ["red", "green", "blue"]

在子構(gòu)造函數(shù)中,通過調(diào)用父構(gòu)造函數(shù)的call方法來實(shí)現(xiàn)繼承,于是SubType的每個(gè)實(shí)例都會將SuperType 中的屬性復(fù)制一份。

缺點(diǎn):

只能繼承父類的實(shí)例屬性和方法,不能繼承原型屬性/方法

無法實(shí)現(xiàn)復(fù)用,每個(gè)子類都有父類實(shí)例函數(shù)的副本,影響性能

更多繼承方案查看我之前的文章。JavaScript常用八種繼承方案

call的模擬實(shí)現(xiàn)
以下內(nèi)容參考自 JavaScript深入之call和apply的模擬實(shí)現(xiàn)

先看下面一個(gè)簡單的例子

var value = 1;
var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

bar.call(foo); // 1

通過上面的介紹我們知道,call()主要有以下兩點(diǎn)

1、call()改變了this的指向

2、函數(shù) bar 執(zhí)行了

模擬實(shí)現(xiàn)第一步

如果在調(diào)用call()的時(shí)候把函數(shù) bar()添加到foo()對象中,即如下

var foo = {
    value: 1,
    bar: function() {
        console.log(this.value);
    }
};

foo.bar(); // 1

這個(gè)改動(dòng)就可以實(shí)現(xiàn):改變了this的指向并且執(zhí)行了函數(shù)bar。

但是這樣寫是有副作用的,即給foo額外添加了一個(gè)屬性,怎么解決呢?

解決方法很簡單,用 delete 刪掉就好了。

所以只要實(shí)現(xiàn)下面3步就可以模擬實(shí)現(xiàn)了。

1、將函數(shù)設(shè)置為對象的屬性:foo.fn = bar

2、執(zhí)行函數(shù):foo.fn()

3、刪除函數(shù):delete foo.fn

代碼實(shí)現(xiàn)如下:

// 第一版
Function.prototype.call2 = function(context) {
    // 首先要獲取調(diào)用call的函數(shù),用this可以獲取
    context.fn = this;         // foo.fn = bar
    context.fn();            // foo.fn()
    delete context.fn;        // delete foo.fn
}

// 測試一下
var foo = {
    value: 1
};

function bar() {
    console.log(this.value);
}

bar.call2(foo); // 1

完美!

模擬實(shí)現(xiàn)第二步

第一版有一個(gè)問題,那就是函數(shù) bar 不能接收參數(shù),所以我們可以從 arguments中獲取參數(shù),取出第二個(gè)到最后一個(gè)參數(shù)放到數(shù)組中,為什么要拋棄第一個(gè)參數(shù)呢,因?yàn)榈谝粋€(gè)參數(shù)是 this。

類數(shù)組對象轉(zhuǎn)成數(shù)組的方法上面已經(jīng)介紹過了,但是這邊使用ES3的方案來做。

var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
    args.push("arguments[" + i + "]");
}

參數(shù)數(shù)組搞定了,接下來要做的就是執(zhí)行函數(shù) context.fn()。

context.fn( args.join(",") ); // 這樣不行

上面直接調(diào)用肯定不行,args.join(",")會返回一個(gè)字符串,并不會執(zhí)行。

這邊采用 eval方法來實(shí)現(xiàn),拼成一個(gè)函數(shù)。

eval("context.fn(" + args +")")

上面代碼中args 會自動(dòng)調(diào)用 args.toString() 方法,因?yàn)?b>"context.fn(" + args +")"本質(zhì)上是字符串拼接,會自動(dòng)調(diào)用toString()方法,如下代碼:

var args = ["a1", "b2", "c3"];
console.log(args);
// ["a1", "b2", "c3"]

console.log(args.toString());
// a1,b2,c3

console.log("" + args);
// a1,b2,c3

所以說第二個(gè)版本就實(shí)現(xiàn)了,代碼如下:

// 第二版
Function.prototype.call2 = function(context) {
    context.fn = this;
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push("arguments[" + i + "]");
    }
    eval("context.fn(" + args +")");
    delete context.fn;
}

// 測試一下
var foo = {
    value: 1
};

function bar(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value);
}

bar.call2(foo, "kevin", 18); 
// kevin
// 18
// 1

完美!!

模擬實(shí)現(xiàn)第三步

還有2個(gè)細(xì)節(jié)需要注意:

1、this 參數(shù)可以傳 null 或者 undefined,此時(shí) this 指向 window

2、函數(shù)是可以有返回值的

實(shí)現(xiàn)上面的兩點(diǎn)很簡單,代碼如下

// 第三版
Function.prototype.call2 = function (context) {
    context = context || window; // 實(shí)現(xiàn)細(xì)節(jié) 1
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push("arguments[" + i + "]");
    }

    var result = eval("context.fn(" + args +")");

    delete context.fn
    return result; // 實(shí)現(xiàn)細(xì)節(jié) 2
}

// 測試一下
var value = 2;

var obj = {
    value: 1
}

function bar(name, age) {
    console.log(this.value);
    return {
        value: this.value,
        name: name,
        age: age
    }
}

bar.call2(null); // 2

console.log(bar.call2(obj, "kevin", 18));
// 1
// {
//    value: 1,
//    name: "kevin",
//    age: 18
// }

完美?。。?/p> call和apply模擬實(shí)現(xiàn)匯總

call的模擬實(shí)現(xiàn)

ES3:

Function.prototype.call = function (context) {
    context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push("arguments[" + i + "]");
    }
    var result = eval("context.fn(" + args +")");

    delete context.fn
    return result;
}

ES6:

Function.prototype.call = function (context) {
  context = context || window;
  context.fn = this;

  let args = [...arguments].slice(1);
  let result = context.fn(...args);

  delete context.fn
  return result;
}
apply的模擬實(shí)現(xiàn)

ES3:

Function.prototype.apply = function (context, arr) {
    context = context || window;
    context.fn = this;

    var result;
    // 判斷是否存在第二個(gè)參數(shù)
    if (!arr) {
        result = context.fn();
    } else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push("arr[" + i + "]");
        }
        result = eval("context.fn(" + args + ")");
    }

    delete context.fn
    return result;
}

ES6:

Function.prototype.apply = function (context, arr) {
    context = context || window;
    context.fn = this;
  
    let result;
    if (!arr) {
        result = context.fn();
    } else {
        result = context.fn(...arr);
    }
      
    delete context.fn
    return result;
}
思考題

callapply 的模擬實(shí)現(xiàn)有沒有問題?歡迎思考評論。

PS: 上期思考題留到下一期講解,下一期介紹重點(diǎn)介紹 bind 原理及實(shí)現(xiàn)

參考
JavaScript深入之call和apply的模擬實(shí)現(xiàn)

MDN之Array.prototype.push()

MDN之Function.prototype.apply()

MDN之Array.prototype.slice()

MDN之Array.isArray()

JavaScript常用八種繼承方案

深入淺出 妙用Javascript中apply、call、bind

進(jìn)階系列目錄

【進(jìn)階1期】 調(diào)用堆棧

【進(jìn)階2期】 作用域閉包

【進(jìn)階3期】 this全面解析

【進(jìn)階4期】 深淺拷貝原理

【進(jìn)階5期】 原型Prototype

【進(jìn)階6期】 高階函數(shù)

【進(jìn)階7期】 事件機(jī)制

【進(jìn)階8期】 Event Loop原理

【進(jìn)階9期】 Promise原理

【進(jìn)階10期】Async/Await原理

【進(jìn)階11期】防抖/節(jié)流原理

【進(jìn)階12期】模塊化詳解

【進(jìn)階13期】ES6重難點(diǎn)

【進(jìn)階14期】計(jì)算機(jī)網(wǎng)絡(luò)概述

【進(jìn)階15期】瀏覽器渲染原理

【進(jìn)階16期】webpack配置

【進(jìn)階17期】webpack原理

【進(jìn)階18期】前端監(jiān)控

【進(jìn)階19期】跨域和安全

【進(jìn)階20期】性能優(yōu)化

【進(jìn)階21期】VirtualDom原理

【進(jìn)階22期】Diff算法

【進(jìn)階23期】MVVM雙向綁定

【進(jìn)階24期】Vuex原理

【進(jìn)階25期】Redux原理

【進(jìn)階26期】路由原理

【進(jìn)階27期】VueRouter源碼解析

【進(jìn)階28期】ReactRouter源碼解析

交流

進(jìn)階系列文章匯總?cè)缦拢瑑?nèi)有優(yōu)質(zhì)前端資料,覺得不錯(cuò)點(diǎn)個(gè)star。

https://github.com/yygmind/blog

我是木易楊,網(wǎng)易高級前端工程師,跟著我每周重點(diǎn)攻克一個(gè)前端面試重難點(diǎn)。接下來讓我?guī)阕哌M(jìn)高級前端的世界,在進(jìn)階的路上,共勉!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/100737.html

相關(guān)文章

  • 進(jìn)階3-4深度解析bind原理、使用場景模擬實(shí)現(xiàn)

    摘要:返回的綁定函數(shù)也能使用操作符創(chuàng)建對象這種行為就像把原函數(shù)當(dāng)成構(gòu)造器,提供的值被忽略,同時(shí)調(diào)用時(shí)的參數(shù)被提供給模擬函數(shù)。 bind() bind() 方法會創(chuàng)建一個(gè)新函數(shù),當(dāng)這個(gè)新函數(shù)被調(diào)用時(shí),它的 this 值是傳遞給 bind() 的第一個(gè)參數(shù),傳入bind方法的第二個(gè)以及以后的參數(shù)加上綁定函數(shù)運(yùn)行時(shí)本身的參數(shù)按照順序作為原函數(shù)的參數(shù)來調(diào)用原函數(shù)。bind返回的綁定函數(shù)也能使用 n...

    guyan0319 評論0 收藏0
  • 進(jìn)階3-5深度解析 new 原理模擬實(shí)現(xiàn)

    摘要:使用指定的參數(shù)調(diào)用構(gòu)造函數(shù),并將綁定到新創(chuàng)建的對象。由構(gòu)造函數(shù)返回的對象就是表達(dá)式的結(jié)果。情況返回以外的基本類型實(shí)例中只能訪問到構(gòu)造函數(shù)中的屬性,和情況完全相反,結(jié)果相當(dāng)于沒有返回值。 定義 new 運(yùn)算符創(chuàng)建一個(gè)用戶定義的對象類型的實(shí)例或具有構(gòu)造函數(shù)的內(nèi)置對象的實(shí)例。 ——(來自于MDN) 舉個(gè)栗子 function Car(color) { this.color = co...

    Baaaan 評論0 收藏0
  • 進(jìn)階 6-2 】深入高階函數(shù)應(yīng)用之柯里化

    摘要:引言上一節(jié)介紹了高階函數(shù)的定義,并結(jié)合實(shí)例說明了使用高階函數(shù)和不使用高階函數(shù)的情況。我們期望函數(shù)輸出,但是實(shí)際上調(diào)用柯里化函數(shù)時(shí),所以調(diào)用時(shí)就已經(jīng)執(zhí)行并輸出了,而不是理想中的返回閉包函數(shù),所以后續(xù)調(diào)用將會報(bào)錯(cuò)。引言 上一節(jié)介紹了高階函數(shù)的定義,并結(jié)合實(shí)例說明了使用高階函數(shù)和不使用高階函數(shù)的情況。后面幾部分將結(jié)合實(shí)際應(yīng)用場景介紹高階函數(shù)的應(yīng)用,本節(jié)先來聊聊函數(shù)柯里化,通過介紹其定義、比較常見的...

    stackvoid 評論0 收藏0
  • 進(jìn)階3-2】JavaScript深入之重新認(rèn)識箭頭函數(shù)的this

    摘要:箭頭函數(shù)的尋值行為與普通變量相同,在作用域中逐級尋找。題目這次通過構(gòu)造函數(shù)來創(chuàng)建一個(gè)對象,并執(zhí)行相同的個(gè)方法。 我們知道this綁定規(guī)則一共有5種情況: 1、默認(rèn)綁定(嚴(yán)格/非嚴(yán)格模式) 2、隱式綁定 3、顯式綁定 4、new綁定 5、箭頭函數(shù)綁定 其實(shí)大部分情況下可以用一句話來概括,this總是指向調(diào)用該函數(shù)的對象。 但是對于箭頭函數(shù)并不是這樣,是根據(jù)外層(函數(shù)或者全局)作用域(...

    Rainie 評論0 收藏0
  • 正在暑假中的《課多周刊》(第1)

    摘要:正在暑假中的課多周刊第期我們的微信公眾號,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。若有幫助,請把課多周刊推薦給你的朋友,你的支持是我們最大的動(dòng)力。原理微信熱更新方案漲知識了,熱更新是以后的標(biāo)配。 正在暑假中的《課多周刊》(第1期) 我們的微信公眾號:fed-talk,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。 若有幫助,請把 課多周刊 推薦給你的朋友,你的支持是我們最大的動(dòng)力。 遠(yuǎn)上寒山石徑...

    liukai90 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<