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

внешний интерфейс исходный код JavaScript React.js

предисловие

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

использовалReactВсе студенты знают, что они часто используютbindсвязыватьthis.

import React, { Component } from 'react';
class TodoItem extends Component{
    constructor(props){
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick(){
        console.log('handleClick');
    }
    render(){
        return  (
            <div onClick={this.handleClick}>点击</div>
        );
    };
}
export default TodoItem;

Затем интервьюер может спросить,bindЧто именно он делал и как это имитировать.

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

Первый взглядbindчто. сверхуReactВ коде видно, чтоbindЗа выполнением следует функция, и каждая функция может выполняться, вызывая ее. Видеть значит верить, слышать ложно. Читатели могут нажимать на консоль шаг за шагомПример 1серединаobj:

var obj = {};
console.log(obj);
console.log(typeof Function.prototype.bind); // function
console.log(typeof Function.prototype.bind());  // function
console.log(Function.prototype.bind.name);  // bind
console.log(Function.prototype.bind().name);  // bound

`Function.prototype.bind`

Отсюда можно сделать вывод 1:

1,bindдаFunctoinв цепочке прототиповFunction.prototypeАтрибут , который может вызывать каждая функция.
2,bindсама является функцией с именемbindВозвращаемое значение также является функцией, и имя функцииbound. (Введите этоbound加上一个空格). понялbindэто функция, вы можете передавать параметры и возвращать значение'bound 'Это тоже функция, и она тоже может передавать параметры, поэтому ее легко написатьПример 2:
объединениеboundотносится к исходной функцииoriginal bindФункция, которая возвращается после этого, легко объяснить.

var obj = {
    name: '若川',
};
function original(a, b){
    console.log(this.name);
    console.log([a, b]);
    return false;
}
var bound = original.bind(obj, 1);
var boundResult = bound(2); // '若川', [1, 2]
console.log(boundResult); // false
console.log(original.bind.name); // 'bind'
console.log(original.bind.length); // 1
console.log(original.bind().length); // 2 返回original函数的形参个数
console.log(bound.name); // 'bound original'
console.log((function(){}).bind().name); // 'bound '
console.log((function(){}).bind().length); // 0

Отсюда можно сделать вывод 2:

1. звонокbindв функцииthisнаправлениеbind()Первый параметр функции.

2. Перейти кbind()Остальные параметры принимаются и обрабатываются,bind()После этого параметры возвращаемой функции также принимаются и обрабатываются, то есть объединяются и обрабатываются.

3, иbind()Послеnameдляbound + 空格 + 调用bind的函数名. Если это анонимная функция, то даbound + 空格.

4.bindПосле функции возвращаемого значения возвращаемое значение после выполнения является исходной функцией (original) Возвращаемое значение.

5.bindпараметры функции (то естьlength)Да1.bindвернулся послеboundПараметры функции являются неопределенными, в соответствии с исходной функцией связанной функции (original) Определяется количество формальных параметров.

Согласно выводу 2: мы можем просто смоделировать упрощенный вариантbindFn

// 第一版 修改this指向,合并参数
Function.prototype.bindFn = function bind(thisArg){
    if(typeof this !== 'function'){
        throw new TypeError(this + 'must be a function');
    }
    // 存储函数本身
    var self = this;
    // 去除thisArg的其他参数 转成数组
    var args = [].slice.call(arguments, 1);
    var bound = function(){
        // bind返回的函数 的参数转成数组
        var boundArgs = [].slice.call(arguments);
        // apply修改this指向,把两个函数的参数合并传给self函数,并执行self函数,返回执行结果
        return self.apply(thisArg, args.concat(boundArgs));
    }
    return bound;
}
// 测试
var obj = {
    name: '若川',
};
function original(a, b){
    console.log(this.name);
    console.log([a, b]);
}
var bound = original.bindFn(obj, 1);
bound(2); // '若川', [1, 2]

Если интервьюер увидит здесь ваш ответ, предполагается, что ваше впечатление должно быть на 60 или 70 баллов. Но мы знаем, что функции можно использовать сnewсоздать экземпляр. Такbind()Как выглядит функция возвращаемого значения?
см. далееПример 3:

var obj = {
    name: '若川',
};
function original(a, b){
    console.log('this', this); // original {}
    console.log('typeof this', typeof this); // object
    this.name = b;
    console.log('name', this.name); // 2
    console.log('this', this);  // original {name: 2}
    console.log([a, b]); // 1, 2
}
var bound = original.bind(obj, 1);
var newBoundResult = new bound(2);
console.log(newBoundResult, 'newBoundResult'); // original {name: 2}

отПример 3виды можно увидетьthisуказал наnew bound()Создайте новый объект.

Можно сделать вывод, что 3:

1,bindизначально указывал наobjнедействителен, другие параметры действительны.

2,new boundВозвращаемое значениеoriginalНовый объект, созданный исходным конструктором функции.originalисходная функцияthisУказывает на этот новый объект. Еще одна статья, которую я написал некоторое время назад:Интервьюер спросил: Могу ли я смоделировать новый оператор, реализующий JS?. Простое резюме:что нового делает:

1. Создается новый объект.
2. Этот объект будет выполнен[[Prototype]](то есть,__proto__)Связь.
3. Новый сгенерированный объект будет привязан к этому вызову функции.
4. ПройтиnewКаждый созданный объект в конечном итоге будет[[Prototype]]ссылка на эту функциюprototypeна объекте.
5. Если функция не возвращает тип объектаObject(ВключатьFunctoin, Array, Date, RegExg, Error),ТакnewВызовы функций в выражениях автоматически возвращают этот новый объект.

Таким образом, это эквивалентноnewКогда звонили,bindфункция возвращаемого значенияboundЧтобы смоделировать реализацию внутриnewреализованная операция. Нечего сказать, сразу к коду.

// 第三版 实现new调用
Function.prototype.bindFn = function bind(thisArg){
    if(typeof this !== 'function'){
        throw new TypeError(this + ' must be a function');
    }
    // 存储调用bind的函数本身
    var self = this;
    // 去除thisArg的其他参数 转成数组
    var args = [].slice.call(arguments, 1);
    var bound = function(){
        // bind返回的函数 的参数转成数组
        var boundArgs = [].slice.call(arguments);
        var finalArgs = args.concat(boundArgs);
        // new 调用时,其实this instanceof bound判断也不是很准确。es6 new.target就是解决这一问题的。
        if(this instanceof bound){
            // 这里是实现上文描述的 new 的第 1, 2, 4 步
            // 1.创建一个全新的对象
            // 2.并且执行[[Prototype]]链接
            // 4.通过`new`创建的每个对象将最终被`[[Prototype]]`链接到这个函数的`prototype`对象上。
            // self可能是ES6的箭头函数,没有prototype,所以就没必要再指向做prototype操作。
            if(self.prototype){
                // ES5 提供的方案 Object.create()
                // bound.prototype = Object.create(self.prototype);
                // 但 既然是模拟ES5的bind,那浏览器也基本没有实现Object.create()
                // 所以采用 MDN ployfill方案 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create
                function Empty(){}
                Empty.prototype = self.prototype;
                bound.prototype = new Empty();
            }
            // 这里是实现上文描述的 new 的第 3 步
            // 3.生成的新对象会绑定到函数调用的`this`。
            var result = self.apply(this, finalArgs);
            // 这里是实现上文描述的 new 的第 5 步
            // 5.如果函数没有返回对象类型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`),
            // 那么`new`表达式中的函数调用会自动返回这个新的对象。
            var isObject = typeof result === 'object' && result !== null;
            var isFunction = typeof result === 'function';
            if(isObject || isFunction){
                return result;
            }
            return this;
        }
        else{
            // apply修改this指向,把两个函数的参数合并传给self函数,并执行self函数,返回执行结果
            return self.apply(thisArg, finalArgs);
        }
    };
    return bound;
}

Когда интервьюер видит такой код реализации, это, по сути, полная оценка, а в душе монолог: Этот парень/девушка неплохи. Но, может быть, спроситьthis instanceof boundнекорректный вопрос. упоминалось в комментариях вышеthis instanceof boundТоже не очень точноES6 new.targetХорошее решение этой проблемы, давайте возьмемПример 4:

instanceofНеточный,ES6 new.targetОчень хорошее решение этой проблемы

function Student(name){
    if(this instanceof Student){
        this.name = name;
        console.log('name', name);
    }
    else{
        throw new Error('必须通过new关键字来调用Student。');
    }
}
var student = new Student('若');
var notAStudent = Student.call(student, '川'); // 不抛出错误,且执行了。
console.log(student, 'student', notAStudent, 'notAStudent');

function Student2(name){
    if(typeof new.target !== 'undefined'){
        this.name = name;
        console.log('name', name);
    }
    else{
        throw new Error('必须通过new关键字来调用Student2。');
    }
}
var student2 = new Student2('若');
var notAStudent2 = Student2.call(student2, '川');
console.log(student2, 'student2', notAStudent2, 'notAStudent2'); // 抛出错误

Внимательные студенты могут обнаружить, что эта версия кода не реализована.bindПослеboundфункциональныйnameMDN Function.nameа такжеlengthMDN Function.length. Интервьюер также может найти это и продолжить спрашивать, как этого добиться, или спросить, видели ли вы это.es5-shimреализация исходного кодаL201-L335. если не ограниченоESВерсия.其实可以用ES5изObject.definePropertiesреализовать.

Object.defineProperties(bound, {
    'length': {
        value: self.length,
    },
    'name': {
        value: 'bound ' + self.name,
    }
});

es5-shimреализация исходного кодаbind

Прикрепите исходный код напрямую (с удаленными комментариями и частичными изменениями и т.д.)

var $Array = Array;
var ArrayPrototype = $Array.prototype;
var $Object = Object;
var array_push = ArrayPrototype.push;
var array_slice = ArrayPrototype.slice;
var array_join = ArrayPrototype.join;
var array_concat = ArrayPrototype.concat;
var $Function = Function;
var FunctionPrototype = $Function.prototype;
var apply = FunctionPrototype.apply;
var max = Math.max;
// 简版 源码更复杂些。
var isCallable = function isCallable(value){
    if(typeof value !== 'function'){
        return false;
    }
    return true;
};
var Empty = function Empty() {};
// 源码是 defineProperties
// 源码是bind笔者改成bindFn便于测试
FunctionPrototype.bindFn = function bind(that) {
    var target = this;
    if (!isCallable(target)) {
        throw new TypeError('Function.prototype.bind called on incompatible ' + target);
    }
    var args = array_slice.call(arguments, 1);
    var bound;
    var binder = function () {
        if (this instanceof bound) {
            var result = apply.call(
                target,
                this,
                array_concat.call(args, array_slice.call(arguments))
            );
            if ($Object(result) === result) {
                return result;
            }
            return this;
        } else {
            return apply.call(
                target,
                that,
                array_concat.call(args, array_slice.call(arguments))
            );
        }
    };
    var boundLength = max(0, target.length - args.length);
    var boundArgs = [];
    for (var i = 0; i < boundLength; i++) {
        array_push.call(boundArgs, '$' + i);
    }
    // 这里是Function构造方式生成形参length $1, $2, $3...
    bound = $Function('binder', 'return function (' + array_join.call(boundArgs, ',') + '){ return binder.apply(this, arguments); }')(binder);

    if (target.prototype) {
        Empty.prototype = target.prototype;
        bound.prototype = new Empty();
        Empty.prototype = null;
    }
    return bound;
};

ты говоришьes5-shimисходный кодbindРеализация, этот код действительно эффективен и строгим. Интервьюер монологически, вы можете быть: вы люди, которых я хочу найти, платить за оплату, вы можетеHRГоворите о следующем.

Наконец, чтобы подвести итог

1,bindдаFunctionв цепочке прототиповFunction.prototypeАтрибут , который является функцией, которая изменяетthisУкажите, объедините параметры, переданные исходной функции, и возвращаемое значение будет новой функцией.
2,bindДоступ к возвращенной функции можно получить черезnewзвоните, когда будет предоставленоthisАргумент игнорируется и указывает наnewПолучился совершенно новый объект. Реализовано внутреннее моделированиеnewоператор.
3.es5-shimРеализация имитации исходного кодаbindвремя использоватьFunctionДостигнутоlength.
На самом деле редко требуется использовать ваши собственные реализованные входные данные в среде сборки. Но интервьюер может проверить много знаний с помощью этого вопроса интервью. НапримерthisЗнание указателей, цепочек прототипов, замыканий, функций и т. д. можно значительно расширить.
Читатели могут указать, что не так или что можно улучшить. Кроме того, если вы считаете, что текст хороший, вы можете поставить лайк, что также является своеобразной поддержкой автора.

Примеры и тестовый код в статье размещены вgithubсерединапривязать фиктивную реализацию github.Реализация моделирования привязки Адрес для предварительного просмотра F12Глядя на вывод консоли, объединитеsourceВид на панели лучше.

// 最终版 删除注释 详细注释版请看上文
Function.prototype.bind = Function.prototype.bind || function bind(thisArg){
    if(typeof this !== 'function'){
        throw new TypeError(this + ' must be a function');
    }
    var self = this;
    var args = [].slice.call(arguments, 1);
    var bound = function(){
        var boundArgs = [].slice.call(arguments);
        var finalArgs = args.concat(boundArgs);
        if(this instanceof bound){
            if(self.prototype){
                function Empty(){}
                Empty.prototype = self.prototype;
                bound.prototype = new Empty();
            }
            var result = self.apply(this, finalArgs);
            var isObject = typeof result === 'object' && result !== null;
            var isFunction = typeof result === 'function';
            if(isObject || isFunction){
                return result;
            }
            return this;
        }
        else{
            return self.apply(thisArg, finalArgs);
        }
    };
    return bound;
}

Ссылаться на

Глубокое понимание перевода OShotokillES6Упрощенная китайская версия — функции главы 3(Хотя я читаю бумажные книги, я рекомендую эту онлайн-книгу)
MDN Function.prototype.bind
Ю Ю: имитационная реализация глубокого связывания JavaScript
Хоу Се в «React State Management and Isomorphism»: от вопроса на собеседовании до «Возможно, я читал поддельный исходный код»

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

Изучите общую архитектуру исходного кода 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].

若川视野