предисловие
Здравствуйте, яВакагава. Это третья статья из серии «Вопросы интервьюера», призванная помочь читателям улучшить
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
может не совпадать с фактическим значением, видимым методом: если эта функция находится внестрогий режимниже он указан какnull
undefined
apply
call
// 例子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]
typeof
7
тип (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].