Интервьюер спросил: Могу ли я смоделировать вызов и применить методы JS?

внешний интерфейс Шаблоны проектирования JavaScript функциональное программирование опрос

предисловие

Здравствуйте, яВакагава. Это третья статья из серии «Вопросы интервьюера», призванная помочь читателям улучшитьJSбазовые знания, в том числеnew、call、apply、this、继承связанная информация.
面试官问系列Статья выглядит следующим образом: Заинтересованные читатели могут нажать, чтобы прочитать.
1.Интервьюер спросил: Могу ли я смоделировать новый оператор, реализующий JS?
2.Интервьюер спросил: Могу ли я смоделировать метод привязки, реализующий JS?
3.Интервьюер спросил: Могу ли я смоделировать вызов и применить методы JS?
4.Интервьюер спросил: этот пункт JS
5.Интервьюер спросил: наследование JS

Я написал два«Интервьюер спросил: Можно ли реализовать симуляцию?JSизnewоператор"а также«Интервьюер спросил: Можно ли реализовать симуляцию?JSизbindметод"

который имитируетbindметод используется, когдаcallа такжеapplyИсправлятьthisнаправление. Но интервьюер может спросить: Могу ли я использоватьcallа такжеapplyосознать это. Это означает, что моделирование должно быть реализованоcallа такжеapply.

Прикреплен абзац из предыдущей статьи: уже существует множество реализаций симуляции.callа такжеapplyПочему ты снова должен писать это сам? Обучение похоже на большую гору: люди взбираются на гору по разным дорогам и делятся пейзажами, которые видят. Возможно, вы не сможете увидеть пейзаж, который видят другие, и почувствовать настроение других. Только когда вы отправляетесь в горы самостоятельно, вы можете увидеть разные пейзажи и испытать более глубокие переживания.

пройти первымMDNузнатьcallа такжеapply

Документация MDN: Function.prototype.call()
грамматика

fun.call(thisArg, arg1, arg2, ...)

thisArg
существуетfunуказывается при выполнении функцииthisстоимость. Следует отметить, что указанныйthisЗначение не обязательно должно быть реальным значением при выполнении функции.thisзначение, если эта функция находится внестрогий режимниже он указан какnullа такжеundefinedизthisЗначение будет автоматически указывать на глобальный объект (в браузере этоwindowобъект), в то время как значение является примитивным значением (число, строка, логическое значение)thisУкажет на объект с автоматической оболочкой для этого примитивного значения.
arg1, arg2, ...
указанный список параметров
возвращаемое значение
Возвращаемое значение — это возвращаемое значение метода, который вы вызвали. Если у метода нет возвращаемого значения, вернитеundefined.

Документация MDN: Function.prototype.apply()

func.apply(thisArg, [argsArray])

thisArg
необязательный. существуетfuncиспользуется при выполнении функцииthisстоимость. осторожность,thisможет не совпадать с фактическим значением, видимым методом: если эта функция находится внестрогий режимниже он указан какnullилиundefinedавтоматически заменяется указателем на глобальный объект, а исходное значение упаковывается.
argsArray
необязательный. Массив или подобный массиву объект, элементы массива которого будут переданы как отдельные аргументы вfuncфункция. Если значение этого параметра равноnullилиundefined,则表示不需要传入任何参数。 отECMAScript 5Для начала можно использовать массивоподобные объекты.
возвращаемое значение
Результат вызова функции с указанными this значением и параметрами. смотреть прямоПример 1

callа такжеapplyсходства и различия

Тот же пункт:
1,callа такжеapplyпервый параметрthisArg, обеfuncуказывается во время выполненияthis. а также,thisможет не совпадать с фактическим значением, видимым методом: если эта функция находится внестрогий режимниже он указан какnullundefined

applycall

// 例子1:浏览器环境 非严格模式下
var doSth = function(a, b){
    console.log(this);
    console.log([a, b]);
}
doSth.apply(null, [1, 2]); // this是window  // [1, 2]
doSth.apply(0, [1, 2]); // this 是 Number(0) // [1, 2]
doSth.apply(true); // this 是 Boolean(true) // [undefined, undefined]
doSth.call(undefined, 1, 2); // this 是 window // [1, 2]
doSth.call('0', 1, {a: 1}); // this 是 String('0') // [1, {a: 1}]
// 例子2:浏览器环境 严格模式下
'use strict';
var doSth2 = function(a, b){
    console.log(this);
    console.log([a, b]);
}
doSth2.call(0, 1, 2); // this 是 0 // [1, 2]
doSth2.apply('1'); // this 是 '1' // [undefined, undefined]
doSth2.apply(null, [1, 2]); // this 是 null // [1, 2]

typeof7тип (undefined number string boolean symbol object function), автор еще раз проверил это:Далее проверяется первая точка этой же точки.thisзначениеcallа такжеapplyпервый параметрthisArg, в нестрогом режиме,thisArgзначение указано какnullилиundefinedВремяthisЗначение автоматически заменяется указателем на глобальный объект, а исходное значение автоматически упаковывается, т.е.new Object().

вновь познакомилсяcallа такжеapplyнайду:Они имеют тот же эффект, изменяют функцию вthisуказывает на первый параметрthisArg, если указать количество параметров, можно использоватьcall, если не понятно, можно использоватьapply. То есть совершенно необязательно использоватьcall, при использованииapplyзаменять.
Другими словами, нам нужно только смоделировать достижениеapply,callЕго можно разместить в массиве по количеству параметров, что дастapplyВот и все.

Реализация моделированияapply

Теперь, когда вы готовы смоделировать реализациюapply, тогда посмотриES5Технические характеристики.ES5规范 英文版,ES5规范 中文版.applyСпецификация следующегоcallСпецификация, вы можете нажать, чтобы открыть новую вкладку для просмотра, вот часть выдержки.

Function.prototype.apply (thisArg, argArray)
когдаthisArgа такжеargArrayдля параметров вfuncвызов на объектapplyметод, используя следующие шаги:

1. ЕслиIsCallable(func)даfalse, затем бросаетTypeErrorаномальный.
2. ЕслиargArrayдаnullилиundefined, затем возвращаетthisArgтак какthisзначение и вызов с пустым списком параметровfuncиз[[Call]]Результат внутреннего метода.
3. Вернуться к предложениюthisArgтак какthisзначение и вызов с пустым списком параметровfuncиз[[Call]]Результат внутреннего метода.
4. ЕслиType(argArray)нетObject, затем бросаетTypeErrorаномальный.
5~8 опущены
9. ОбеспечьтеthisArgтак какthisзначение и закончить сargListв качестве списка аргументов вызовитеfuncиз[[Call]]Внутренний метод, возвращающий результат.
applyметодlengthНедвижимость2.

поступающий извнеthisArgЗначение изменяется и становитсяthisстоимость.thisArgдаundefinedилиnullон будет заменен глобальным объектом, и все остальные значения будут примененыToObjectи результат какthisзначение, которое является изменением, внесенным в третье издание.

Объединив вышеизложенное и спецификацию, как поместить функцию вthisуказывает на первый параметрthisArgЧто ж, это проблема. Пожалуйста, выходиПример 3:

// 浏览器环境 非严格模式下
var doSth = function(a, b){
    console.log(this);
    console.log(this.name);
    console.log([a, b]);
}
var student = {
    name: '若川',
    doSth: doSth,
};
student.doSth(1, 2); // this === student // true // '若川' // [1, 2]
doSth.apply(student, [1, 2]); // this === student // true // '若川' // [1, 2]

Могусделать вывод 1: в объектеstudentдобавить функциюdoSth, а затем выполнить эту функцию, в этой функцииthisуказывает на этот объект. То есть вы можетеthisArgК вышеупомянуту добавляется новая функция вызова, и эта функция может быть удалена после выполнения. Зная это, давайте попробуем легко реализовать первую версию:

// 浏览器环境 非严格模式
function getGlobalObject(){
    return this;
}
Function.prototype.applyFn = function apply(thisArg, argsArray){ // `apply` 方法的 `length` 属性是 `2`。
    // 1.如果 `IsCallable(func)` 是 `false`, 则抛出一个 `TypeError` 异常。
    if(typeof this !== 'function'){
        throw new TypeError(this + ' is not a function');
    }

    // 2.如果 argArray 是 null 或 undefined, 则
    // 返回提供 thisArg 作为 this 值并以空参数列表调用 func 的 [[Call]] 内部方法的结果。
    if(typeof argsArray === 'undefined' || argsArray === null){
        argsArray = [];
    }
    
    // 3.如果 Type(argArray) 不是 Object, 则抛出一个 TypeError 异常 .
    if(argsArray !== new Object(argsArray)){
        throw new TypeError('CreateListFromArrayLike called on non-object');
    }

    if(typeof thisArg === 'undefined' || thisArg === null){
        // 在外面传入的 thisArg 值会修改并成为 this 值。
        // ES3: thisArg 是 undefined 或 null 时它会被替换成全局对象 浏览器里是window
        thisArg = getGlobalObject();
    }

    // ES3: 所有其他值会被应用 ToObject 并将结果作为 this 值,这是第三版引入的更改。
    thisArg = new Object(thisArg);
    var __fn = '__fn';
    thisArg[__fn] = this;
    // 9.提供 thisArg 作为 this 值并以 argList 作为参数列表,调用 func 的 [[Call]] 内部方法,返回结果
    var result = thisArg[__fn](...argsArray);
    delete thisArg[__fn];
    return result;
};

После реализации первого издания легко найти две проблемы:

  • 1.__fnПроблема с одноимённым покрытием,thisArgобъект имеет__fn, он был перезаписан, а затем удален.

На вопрос 1Решение первое: используйтеES6 Sybmol()уникальный. могло быть симуляциейES3Методы. А если интервьюер не разрешит? Решение 2. Используйте его самостоятельноMath.random()Моделирование для достижения уникальногоkey. Вы можете напрямую использовать сгенерированную метку времени во время интервью.

// 生成UUID 通用唯一识别码
// 大概生成 这样一串 '18efca2d-6e25-42bf-a636-30b8f9f2de09'
function generateUUID(){
    var i, random;
    var uuid = '';
    for (i = 0; i < 32; i++) {
        random = Math.random() * 16 | 0;
        if (i === 8 || i === 12 || i === 16 || i === 20) {
            uuid += '-';
        }
        uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random))
            .toString(16);
    }
    return uuid;
}
// 简单实现
// '__' + new Date().getTime();

если этоkeyЕсли в этом объекте все еще есть объекты, на всякий случай можно выполнить операцию кэширования. Например следующий код:

var student = {
    name: '若川',
    doSth: 'doSth',
};
var originalVal = student.doSth;
var hasOriginalVal = student.hasOwnProperty('doSth');
student.doSth = function(){};
delete student.doSth;
// 如果没有,`originalVal`则为undefined,直接赋值新增了一个undefined,这是不对的,所以需判断一下。
if(hasOriginalVal){
    student.doSth = originalVal;
}
console.log('student:', student); // { name: '若川', doSth: 'doSth' }
  • 2. используетсяES6расширитель...
    Решение первое: используйтеevalдля выполнения функции.

evalРазобрать строку в код для выполнения.
Документация MDN: eval
грамматика

eval(string)

параметр
string
выражатьJavaScriptСтрока выражений, операторов или последовательность операторов. Выражения могут содержать переменные, а также свойства существующих объектов.
возвращаемое значение
Возвращаемое значение после выполнения указанного кода. Если возвращаемое значение пусто, вернутьundefined
Решение 2: Но если интервьюер не разрешает использоватьevalНу, в конце концовevalэто дьявол. может быть использованnew Function()для создания исполнительной функции.Документация MDN: Функция
грамматика

new Function ([arg1[, arg2[, ...argN]],] functionBody)

параметр
arg1, arg2, ... argN
Имена параметров, используемых функциями, должны быть правильно названы. имя параметра является допустимымJavaScriptСтрока идентификаторов или список допустимых строк, разделенных запятыми; например.“×”,“theValue”,или“A,B”.
functionBody
Содержащее определение функцииJavaScriptСтроки утверждений.
Давайте рассмотрим два примера:

简单例子:
var sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 6));
// 稍微复杂点的例子:
var student = {
    name: '若川',
    doSth: function(argsArray){
        console.log(argsArray);
        console.log(this.name);
    }
};
// var result = student.doSth(['Rowboat', 18]);
// 用new Function()生成函数并执行返回结果
var result = new Function('return arguments[0][arguments[1]](arguments[2][0], arguments[2][1])')(student, 'doSth', ['Rowboat', 18]);
// 个数不定
// 所以可以写一个函数生成函数代码:
function generateFunctionCode(argsArrayLength){
    var code = 'return arguments[0][arguments[1]](';
    for(var i = 0; i < argsArrayLength; i++){
        if(i > 0){
            code += ',';
        }
        code += 'arguments[2][' + i + ']';
    }
    code += ')';
    // return arguments[0][arguments[1]](arg1, arg2, arg3...)
    return code;
}

ты можешь не знатьES3、ES5серединаundefinedможно изменить

Вероятно, большинство людей не знают.ES5Хотя его нельзя изменить в глобальной области, его также можно изменить в локальной области.Если вы мне не верите, вы можете скопировать следующий тестовый код и выполнить его в консоли. Хотя при нормальных обстоятельствах не модифицировать его.

function test(){
    var undefined = 3;
    console.log(undefined); // chrome下也是 3
}
test();

Итак, определите переменнуюaне так лиundefined, более строгая схемаtypeof a === 'undefined'илиa === void 0;Здесь используетсяvoid,voidРоль заключается в оценке выражения, которое всегда возвращаетundefinedТакже может быть написаноvoid(0). Больше можно посмотреть韩子迟этой статьи:Зачем использовать «void 0» вместо «undefined»После решения этих проблем относительно легко реализовать следующий код.

использоватьnew Function()смоделированныйapply

// 浏览器环境 非严格模式
function getGlobalObject(){
    return this;
}
function generateFunctionCode(argsArrayLength){
    var code = 'return arguments[0][arguments[1]](';
    for(var i = 0; i < argsArrayLength; i++){
        if(i > 0){
            code += ',';
        }
        code += 'arguments[2][' + i + ']';
    }
    code += ')';
    // return arguments[0][arguments[1]](arg1, arg2, arg3...)
    return code;
}
Function.prototype.applyFn = function apply(thisArg, argsArray){ // `apply` 方法的 `length` 属性是 `2`。
    // 1.如果 `IsCallable(func)` 是 `false`, 则抛出一个 `TypeError` 异常。
    if(typeof this !== 'function'){
        throw new TypeError(this + ' is not a function');
    }
    // 2.如果 argArray 是 null 或 undefined, 则
    // 返回提供 thisArg 作为 this 值并以空参数列表调用 func 的 [[Call]] 内部方法的结果。
    if(typeof argsArray === 'undefined' || argsArray === null){
        argsArray = [];
    }
    // 3.如果 Type(argArray) 不是 Object, 则抛出一个 TypeError 异常 .
    if(argsArray !== new Object(argsArray)){
        throw new TypeError('CreateListFromArrayLike called on non-object');
    }
    if(typeof thisArg === 'undefined' || thisArg === null){
        // 在外面传入的 thisArg 值会修改并成为 this 值。
        // ES3: thisArg 是 undefined 或 null 时它会被替换成全局对象 浏览器里是window
        thisArg = getGlobalObject();
    }
    // ES3: 所有其他值会被应用 ToObject 并将结果作为 this 值,这是第三版引入的更改。
    thisArg = new Object(thisArg);
    var __fn = '__' + new Date().getTime();
    // 万一还是有 先存储一份,删除后,再恢复该值
    var originalVal = thisArg[__fn];
    // 是否有原始值
    var hasOriginalVal = thisArg.hasOwnProperty(__fn);
    thisArg[__fn] = this;
    // 9.提供 `thisArg` 作为 `this` 值并以 `argList` 作为参数列表,调用 `func` 的 `[[Call]]` 内部方法,返回结果。
    // ES6版
    // var result = thisArg[__fn](...args);
    var code = generateFunctionCode(argsArray.length);
    var result = (new Function(code))(thisArg, __fn, argsArray);
    delete thisArg[__fn];
    if(hasOriginalVal){
        thisArg[__fn] = originalVal;
    }
    return result;
};

с помощью моделированияapplyРеализация моделированияcall

Function.prototype.callFn = function call(thisArg){
    var argsArray = [];
    var argumentsLength = arguments.length;
    for(var i = 0; i < argumentsLength - 1; i++){
        // argsArray.push(arguments[i + 1]);
        argsArray[i] = arguments[i + 1];
    }
    console.log('argsArray:', argsArray);
    return this.applyFn(thisArg, argsArray);
}
// 测试例子
var doSth = function (name, age){
    var type = Object.prototype.toString.call(this);
    console.log(typeof doSth);
    console.log(this === firstArg);
    console.log('type:', type);
    console.log('this:', this);
    console.log('args:', [name, age], arguments);
    return 'this--';
};

var name = 'window';

var student = {
    name: '若川',
    age: 18,
    doSth: 'doSth',
    __fn: 'doSth',
};
var firstArg = student;
var result = doSth.applyFn(firstArg, [1, {name: 'Rowboat'}]);
var result2 = doSth.callFn(firstArg, 1, {name: 'Rowboat'});
console.log('result:', result);
console.log('result2:', result2);

Если вы будете внимательны, то обнаружите, что это предложение аннотированоargsArray.push(arguments[i + 1]);, по фактуpushметод, внутри также есть слой петли. Так что теоретически не используйтеpushПроизводительность будет лучше. Основываясь на этом, интервьюер может также задавать вопросы о временной и пространственной сложности.

// 看看V8引擎中的具体实现:
function ArrayPush() {
    var n = TO_UINT32( this.length );    // 被push的对象的length
    var m = %_ArgumentsLength();     // push的参数个数
    for (var i = 0; i < m; i++) {
        this[ i + n ] = %_Arguments( i );   // 复制元素     (1)
    }
    this.length = n + m;      // 修正length属性的值    (2)
    return this.length;
};

На этом запись в основном закончена.Вы также можете обнаружить, что она написана в нестрогом режиме.thisArgПримитивные значения заворачиваются в объекты, добавляются и выполняются функции, а затем удаляются. В строгом режиме исходное значение все еще не реализовано, а в случае, если объект является замороженным объектом,Object.freeze({}), к этому объекту невозможно добавить свойства. Так что этот метод можно рассматривать только как упрощенную реализацию в нестрогом режиме. Наконец, подведем итоги.

Суммировать

пройти черезMDNзнатьcallа такжеapply,читатьES5спецификация, чтобы имитировать реализациюapplyЗатем понялcall.
заключается в том, чтобы добавить вызов объекта, используяapplyФункция выполняется, в это время вызывающая функцияthisуказывает на этоthisArg, и вернуть результат. вызвалES6 Symbol,ES6расширение...,eval,new Function(), строгий режим и т.д.
На самом деле реальные бизнес-сценарии не нужно моделировать и реализовывать.callа такжеapply, после всегоES3предоставленный метод. Но интервьюер может использовать этот вопрос для собеседования, чтобы проверить многие базовые знания кандидата. Такие как:call,applyиспользование.ES6 Symbol,ES6расширение...,eval,new Function(), строгий режим, четная временная сложность и пространственная сложность и т. д.
Читатели могут указать, что не так или что можно улучшить. Кроме того, если вы считаете, что текст хороший, вы можете поставить лайк, что также является своеобразной поддержкой автора.

// 最终版版 删除注释版,详细注释看文章
// 浏览器环境 非严格模式
function getGlobalObject(){
    return this;
}
function generateFunctionCode(argsArrayLength){
    var code = 'return arguments[0][arguments[1]](';
    for(var i = 0; i < argsArrayLength; i++){
        if(i > 0){
            code += ',';
        }
        code += 'arguments[2][' + i + ']';
    }
    code += ')';
    return code;
}
Function.prototype.applyFn = function apply(thisArg, argsArray){
    if(typeof this !== 'function'){
        throw new TypeError(this + ' is not a function');
    }
    if(typeof argsArray === 'undefined' || argsArray === null){
        argsArray = [];
    }
    if(argsArray !== new Object(argsArray)){
        throw new TypeError('CreateListFromArrayLike called on non-object');
    }
    if(typeof thisArg === 'undefined' || thisArg === null){
        thisArg = getGlobalObject();
    }
    thisArg = new Object(thisArg);
    var __fn = '__' + new Date().getTime();
    var originalVal = thisArg[__fn];
    var hasOriginalVal = thisArg.hasOwnProperty(__fn);
    thisArg[__fn] = this;
    var code = generateFunctionCode(argsArray.length);
    var result = (new Function(code))(thisArg, __fn, argsArray);
    delete thisArg[__fn];
    if(hasOriginalVal){
        thisArg[__fn] = originalVal;
    }
    return result;
};
Function.prototype.callFn = function call(thisArg){
    var argsArray = [];
    var argumentsLength = arguments.length;
    for(var i = 0; i < argumentsLength - 1; i++){
        argsArray[i] = arguments[i + 1];
    }
    return this.applyFn(thisArg, argsArray);
}

Расширенное чтение

Шаблоны проектирования JavaScript и практика разработки — Глава 2 Глава 2  это, вызов и применение
JS Magic Hall: снова встречайте Function.prototype.call
Не используйте методы вызова и применения для имитации метода связывания ES5.
Углубленный вызов JavaScript и реализация имитационного моделирования

Избранные статьи автора

Изучите общую архитектуру исходного кода sentry и создайте собственный SDK для мониторинга исключений переднего плана.
Изучите общую архитектуру исходного кода lodash и создайте собственную библиотеку классов функционального программирования.
Изучите общую архитектуру исходного кода подчеркивания и создайте собственную библиотеку классов функционального программирования.
Изучите общую архитектуру исходного кода jQuery и создайте собственную библиотеку js.
Интервьюер спросил: наследование JS
Интервьюер спросил: этот пункт JS
Интервьюер спросил: Могу ли я смоделировать вызов и применить методы JS?
Интервьюер спросил: Могу ли я смоделировать метод привязки, реализующий JS?
Интервьюер спросил: Могу ли я смоделировать новый оператор, реализующий JS?
Внешний интерфейс использует сканер puppeteer для создания PDF-файла «React.js Book» и его слияния.

о

Автор: Чанг ИВакагаваНазвание смешано в реках и озерах. По дороге на фронт | Энтузиасты РРТ | Знаю очень мало, только хорошо учусь.
личный блог
segmentfaultПередняя колонка обзора, открылПередний планКолонка, добро пожаловать на внимание ~
Колонка самородков, добро пожаловать, обратите внимание~
Знайте переднюю колонку видения, открылПередний планКолонка, добро пожаловать на внимание ~
github blog, спроситьstar^_^~

Публичный аккаунт WeChat Ruochuan Vision

Может быть более интересным общедоступный аккаунт WeChat, нажмите и удерживайте, чтобы отсканировать код, чтобы следовать. Вы также можете добавить WeChatruochuan12, укажите источник и втяните вас в [Front-end Vision Exchange Group].

若川视野