[Расширенная фаза 3-3] Углубленный анализ принципов вызова и применения, сценариев использования и реализации

внешний интерфейс JavaScript

Обновление: Спасибо за вашу поддержку. Недавно я бросил сводку данных для всех, чтобы прочитать систему. В будущем будет больше контента и больше оптимизации.Нажмите здесь, чтобы просмотреть

------ Далее идет текст ------

В предыдущей статье было подробно описано использование этого.Если вы не понимаете этого, проверьте [Дополнительно 3-1].

вызов() и применение()

Метод call() вызывает функцию с указаннымthisзначение и отдельно предоставляемые параметры (список параметров).

call()а такжеapply()Разница в том, чтоcall()метод принимаетсписок из нескольких параметров,а такжеapply()метод принимаетмассив с несколькими параметрами

Например:

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

func.call(this, arg1, arg2); // 使用 call,参数列表
func.apply(this, [arg1, arg2]) // 使用 apply,参数数组

сцены, которые будут использоваться

Вот некоторые распространенные варианты использования:

1. Объединить два массива

var vegetables = ['parsnip', 'potato'];
var moreVegs = ['celery', 'beetroot'];

// 将第二个数组融合进第一个数组
// 相当于 vegetables.push('celery', 'beetroot');
Array.prototype.push.apply(vegetables, moreVegs);
// 4

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

Когда второй массив (как в примереmoreVegs) слишком велик, не используйте этот метод для объединения массивов, так какСуществует ограничение на количество аргументов, которые может принимать функция.из.不同的引擎有不同的限制,JS核心限制在 65535,有些引擎会抛出异常,有些不抛出异常但丢失多余参数。

Как это решить? Путь этоРазделите массив параметров на части и выполните цикл в целевом методе.

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

// 验证代码
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. Получить максимальное и минимальное значения в массиве

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

Зачем использовать это, потому что массивыnumbersне сам по себеmax метод, ноMath Да, так вот использование call / applyиспользоватьMath.maxметод.

3. Проверьте, является ли это массивом

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()чтобы получить тип каждого объекта, но разные объектыtoString()Существуют разные реализации, поэтомуObject.prototype.toString()для обнаружения необходимоcall() / apply()form для вызова, передавая объект для проверки в качестве первого аргумента.

еще одинПроверьте, является ли это массивомМетоды

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функция определяетthisзначение, то.bindВозвращает новую функцию, которая всегдаObject.prototype.toString设置为传入参数。 на самом деле эквивалентноObject.prototype.toString.call().

вот одинпомещениедаtoString()методне распространяется

Object.prototype.toString = function() {
    return '';
}
isArray([1, 2, 3]);
// false

4. Массивоподобные объекты используют методы массива

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 不同环境下数据不同
// (505) ["h1", html.gr__hujiang_com, head, meta, ...] 

Объект-массив имеет следующие два свойства

  • 1. Имеет: нижний индекс числового индекса, указывающий на элемент объекта иlengthАтрибуты
  • 2. Не имеет: таких какpush,shift,forEachтак же какindexOf такие методы, как объекты массива, имеют

Чтобы уточнить, подобный массиву объектобъект. В JS есть методмассивныйструктура объекта, напримерargumentsобъекты и те, которые возвращаются DOM APINodeListОбъекты относятся к массивам, как объекты, и массивные объекты не могут быть использованыpush/pop/shift/unshiftи другие методы массива, черезArray.prototype.slice.callПреобразованный в реальный массив, вы можете использоватьArrayвсе способы ниже.

Массивоподобный объект в массивдругие методы:

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

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

Array.from()Два типа объектов могут быть превращены в настоящие массивы:массивныйобъект иПроходной(итерируемые) объекты (включая новые структуры данных ES6 Set и Map).

PS расширение один: зачем проходитьArray.prototype.slice.call()Можете ли вы преобразовать массивоподобный объект в массив?

на самом деле это очень легко,sliceБудуArray-likeОбъект помещается в новыйArrayв.

Следующий код MDN оsliceПолифилл, ссылка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 расширение два:пройти черезArray.prototype.slice.call()Это достаточно? В чем проблема?

существуетНе поддерживается в более ранних версиях IEпройти черезArray.prototype.slice.call(args)Преобразуйте массивоподобный объект в массив, потому что более ранняя версия IE (IE DOMобъектcomРеализован в виде объекта, js объекта иcomОбъект не может быть преобразован.

Совместимое письмо выглядит следующим образом:

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Третий: зачем нужны массивоподобные объекты? Или почему объект, подобный массиву, кажется, решает проблему?

Типизированные массивы JavaScript похожи на массивы.объекти предоставляет механизм доступа к необработанным двоичным данным.ArrayСохраненные объекты могут динамически увеличиваться и уменьшаться и могут хранить любое значение JavaScript. Движок JavaScript выполняет некоторые внутренние оптимизации, чтобы операции с массивами могли быть быстрыми. Однако по мере того, как веб-приложения становятся все более и более мощными, особенно с новыми дополнениями, такими как редактирование аудио и видео, доступ к необработанным данным из веб-сокетов и т. д., становится ясно, что иногда, если вы используете код JavaScript, вы можете быстро и легко использовать типизированные массивы. ● Манипуляции с необработанными двоичными данными, которые могут быть очень полезными.

Одним словом, сложные данные можно обрабатывать быстрее.

5. Вызов родительского конструктора для реализации наследования

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"]

В дочернем конструкторе вызов родительского конструктораcallметод реализации наследования, поэтомуSubTypeКаждый экземпляр волиSuperTypeСделайте копию свойств в .

недостаток:

  • Может наследовать только от родительского классапримерСвойства и методы, не могут наследовать свойства/методы прототипа
  • Невозможно добиться повторного использования, каждый подкласс имеет копию функции экземпляра родительского класса, что влияет на производительность.

Чтобы узнать больше о схемах наследования, ознакомьтесь с моей предыдущей статьей.Восемь распространенных схем наследования в JavaScript

Макетная реализация вызова

Следующая ссылка отУглубленный вызов JavaScript и реализация имитационного моделирования

Давайте рассмотрим следующий простой пример

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

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

bar.call(foo); // 1

Из приведенного выше введения мы знаем, чтоcall()Есть два основных момента

  • 1,call()Изменил суть этого
  • 2. Функцияbarказнен

Первый шаг в симуляции

если звонишьcall()при вводе функцииbar()добавить вfoo()объект, то есть следующим образом

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

foo.bar(); // 1

Это изменение может быть достигнуто: измените указатель this и выполните функциюbar.

Но написание этого имеет побочный эффектfooДобавлен дополнительный атрибут, как решить?

Решение простое, используйтеdeleteПросто удалите его.

Итак, пока реализованы следующие 3 шага, моделирование может быть реализовано.

  • 1. Установить функцию как свойство объекта:foo.fn = bar
  • 2. Функция исполнения:foo.fn()
  • 3. Функция удаления:delete foo.fn

Код реализован следующим образом:

// 第一版
Function.prototype.call2 = function(context) {
    // 首先要获取调用call的函数,用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

Идеально!

Имитационная реализация второго шага

В первой версии была проблема, и это была функцияbarне может получать параметры, поэтому мы можем начать сargumentsПолучите параметры из , выньте предпоследние параметры и поместите их в массив, почему вы должны отбросить первый параметр, потому что первый параметрthis.

Метод преобразования массивоподобных объектов в массивы был представлен выше, но здесь мы используем решение ES3.

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

Массив параметров готов, осталось выполнить функциюcontext.fn().

context.fn( args.join(',') ); // 这样不行

Вышеупомянутый прямой вызов, конечно, не работает,args.join(',')вернет строку и не будет выполняться.

принято здесьevalметод достижения, записанный в функцию.

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

в приведенном выше кодеargsбудет вызываться автоматическиargs.toString()метод, потому что'context.fn(' + args +')'По сути конкатенация строк, она будет вызываться автоматически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

Итак реализована вторая версия, код такой:

// 第二版
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

Идеально! !

Имитационная реализация третьего шага

Следует отметить еще 2 детали:

  • 1. Этот параметр можно передатьnullилиundefined, в это время это указывает на окно
  • 2. Параметр this может передавать данные базового типа, а собственный вызов будет автоматически преобразован с помощью Object().
  • 3. Функции могут возвращать значения

Реализовать вышеперечисленные три пункта очень просто, код выглядит следующим образом

// 第三版
Function.prototype.call2 = function (context) {
    context = context ? Object(context) : window; // 实现细节 1 和 2
    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; // 实现细节 2
}

// 测试一下
var value = 2;

var obj = {
    value: 1
}

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

function foo() {
    console.log(this);
}

bar.call2(null); // 2
foo.call2(123); // Number {123, fn: ƒ}

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

Идеально! ! !

Сводка вызовов и применений имитационного моделирования

Макетная реализация вызова

ES3:

Function.prototype.call = function (context) {
    context = context ? Object(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;
}

ЭС6:

Function.prototype.call = function (context) {
  context = context ? Object(context) : window; 
  context.fn = this;

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

  delete context.fn
  return result;
}

Макетная реализация применения

ES3:

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

    var result;
    // 判断是否存在第二个参数
    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;
}

ЭС6:

Function.prototype.apply = function (context, arr) {
    context = context ? Object(context) : window; 
    context.fn = this;
  
    let result;
    if (!arr) {
        result = context.fn();
    } else {
        result = context.fn(...arr);
    }
      
    delete context.fn
    return result;
}

мыслительные вопросы

callа такжеapplyЕсть ли проблема с фиктивной реализацией? Комментарии приветствуются.


PS: Мыслительные вопросы предыдущего выпуска зарезервированы для объяснения в следующем выпуске, а следующий выпуск будет посвящен введению.bindПринцип и реализация

Ссылаться на

Углубленный вызов JavaScript и реализация имитационного моделирования

Array.prototype.push() из MDN

Function.prototype.apply() MDN

Array.prototype.slice() из MDN

Array.isArray() из MDN

Восемь распространенных схем наследования в JavaScript

Волшебный JavaScript в простых условиях применить, звонить, связывать