Многие студенты, которые только что вошли в индустрию, говорили мне: «Что делать, если я не могу вспомнить многие API в JavaScript? Всегда тупо непонятно тот метод и тот метод массива, что делать? Способ манипулирования DOM сегодня помнят, а завтра забывают, это действительно расстраивает!"
Некоторые разработчики даже жалуются мне при обсуждении интервью: "Интервьюеры всегда запутались в использовании API, и даже порядок параметров некоторых методов jQuery должен быть четко объяснен!"
На мой взгляд, для метода многократного использования каждый должен иметь "механическую память" и уметь записывать ее от руки. Некоторые API, которые, кажется, никогда не запоминаются, просто недостаточно используются.
Как интервьюер, я никогда не заставляю разработчиков точно «декламировать» API. Вместо этого мне нравится смотреть на собеседника под другим углом:"Поскольку я не могу вспомнить, как его использовать, я расскажу вам, как его использовать, и вы сможете реализовать его!«Внедрение API может не только проверить понимание API интервьюируемым, но и отразить мышление разработчика в области программирования и способность кодировать. Для мотивированных фронтенд-инженеров имитация и внедрение некоторых классических методов должны быть «повседневными», что является относительно базовыми требованиями. .
**В этом разделе, основываясь на известных мне вопросах интервью и моем опыте интервьюера, я выбираю несколько типичных API и освещаю некоторые моменты знаний и основы программирования на JavaScript, реализуя их в разной степени и разными способами. **Изучая содержание этого раздела, я надеюсь, вы сможете не только понять смысл кода, но и научиться делать выводы из одного экземпляра.
Соответствующие точки знаний по темам API следующие:
реализация смещения jQuery
Эта тема развилась из вопросов интервью определенного отдела в Toutiao сегодня. В то время интервьюер спросил: «Как получить расстояние любого элемента в документе от документа
document
расстояние от вершины? "
Учащиеся, знакомые с jQuery, должныoffset
Метод не чужой, он возвращает или задает смещение (положение) совпавшего элемента относительно документа. Объект, возвращаемый этим методом, содержит два целочисленных свойства:top
а такжеleft
Возьмите пиксель. Если вы можете использовать jQuery, мы можем напрямую адаптировать API для получения результата. но,Если реализовано в собственном JavaScript, то есть вручную реализовать jQueryoffset
Метод, как начать?
Есть две основные идеи:
- Реализовано рекурсивно
- пройти через
getBoundingClientRect
Реализация API
рекурсивная реализация
Мы вернемся к источнику, проехавшей целевой элемент, родительский узел элемента целевого элемента, родительский узел родительского узла ... и накапливаться эти пройденные узлы относительно их ближайших узлов предков (иposition
атрибут неstatic
) смещение, доdocument
, сложите, чтобы получить результат.
Среди них нам нужно использовать JavaScriptoffsetTop
для доступа к границе на узле DOM, который относительно ближе всего к себе, иposition
значение неstatic
Вертикальное смещение элемента-предка. Конкретная реализация:
const offset = ele => {
let result = {
top: 0,
left: 0
}
// 当前 DOM 节点的 display === 'none' 时, 直接返回 {top: 0, left: 0}
if (window.getComputedStyle(ele)['display'] === 'none') {
return result
}
let position
const getOffset = (node, init) => {
if (node.nodeType !== 1) {
return
}
position = window.getComputedStyle(node)['position']
if (typeof(init) === 'undefined' && position === 'static') {
getOffset(node.parentNode)
return
}
result.top = node.offsetTop + result.top - node.scrollTop
result.left = node.offsetLeft + result.left - node.scrollLeft
if (position === 'fixed') {
return
}
getOffset(node.parentNode)
}
getOffset(ele, true)
return result
}
Приведенный выше код не сложен для понимания и реализован с использованием рекурсии. если узелnode.nodeType
тип неElement(1)
, то выпрыгнуть; еслиposition
собственностьstatic
, он не включается в расчет, а вводится рекурсия к следующему узлу (его родительскому узлу). Если соответствующее имуществоdisplay
собственностьnone
, он должен возвращать 0 непосредственно в качестве результата.
Эта реализация является хорошей проверкой элементарного использования разработчиком рекурсии и владения методами JavaScript.
Далее пойдем другим путем и воспользуемся относительно новым API:getBoundingClientRect
реализовать jQueryoffset
метод.
getBoundingClientRect
метод
getBoundingClientRect
Метод используется для описания определенного положения элемента.Следующие четыре свойства этого положения относятся к положению верхнего левого угла окна просмотра. Выполните этот метод на узле, и его возвращаемое значение будетDOMRectтип объекта. Этот объект представляет собой прямоугольную коробку, содержащую:left
,top
,right
а такжеbottom
и другие свойства только для чтения.
Пожалуйста, обратитесь к реализации:
const offset = ele => {
let result = {
top: 0,
left: 0
}
// 当前为 IE11 以下,直接返回 {top: 0, left: 0}
if (!ele.getClientRects().length) {
return result
}
// 当前 DOM 节点的 display === 'none' 时,直接返回 {top: 0, left: 0}
if (window.getComputedStyle(ele)['display'] === 'none') {
return result
}
result = ele.getBoundingClientRect()
var docElement = ele.ownerDocument.documentElement
return {
top: result.top + window.pageYOffset - docElement.clientTop,
left: result.left + window.pageXOffset - docElement.clientLeft
}
}
Обратите внимание на следующие детали:
-
node.ownerDocument.documentElement
Использование может быть незнакомым для всех.ownerDocument
Является свойством узла DOM, возвращает верхний слой текущего узла.document
объект.ownerDocument
это документ,documentElement
является корневым узлом. Фактически,ownerDocument
Ниже находятся 2 узла:<!DocType>
documentElement
docElement.clientTop
,clientTop
ширина верхней границы элемента, не включая верхнее поле или отступы. -
Кроме того, реализация этого метода представляет собой простые геометрические операции, граничный случай и обработку совместимости, и ее нетрудно понять.
Как видно из этого заголовка, такая реализация имеет больше смысла, чем изучение API «механического запоминания». С точки зрения интервьюера, я часто даю интервьюеру (разработчику) соответствующие советы по методу, чтобы помочь ему дать окончательное решение.
Соответствующая реализация метода уменьшения массива
Метод массива очень важен: **поскольку массивы — это данные, данные — это состояние, а состояние отражает представление. **Мы не можем быть незнакомы с работой массивов, среди которыхreduce
Метод должен быть более привычным. Я думаю, что этот метод является хорошим воплощением «функциональной» концепции, а также одним из самых популярных направлений исследования.
мы знаемreduce
Этот метод был представлен ES5, и английское объяснение сокращения переводится как «уменьшать, сжимать, восстанавливать и ослаблять». MDN прямо описывает метод как:
The reduce method applies a function against an accumulator and each value of the array (from left-to-right) to reduce it to a single value.
Синтаксис его использования:
arr.reduce(callback[, initialValue])
Здесь мы кратко представляем его.
-
reduce
первый параметрcallback
является ядром, он «складывает» каждый элемент массива, и его последнее возвращаемое значение будет использоваться какreduce
Окончательное возвращаемое значение метода. Он содержит 4 параметра:-
previousValue
означает «последний раз»callback
возвращаемое значение функции -
currentValue
Элемент обрабатывается в массиве обхода -
currentIndex
необязательный, означаетcurrentValue
Соответствующий индекс в массиве. если предусмотреноinitialValue
, начальный номер индекса равен 0, иначе он равен 1 -
array
по желанию, звонитеreduce()
массив
-
-
initialValue
необязательно, как первый вызовcallback
первый параметр времени. если не предусмотреноinitialValue
, то первый элемент массива будетcallback
первый параметр .
reduce
выполнитьrunPromiseInSequence
мы смотрим на этотипичное приложение: запустить промисы по порядку:
const runPromiseInSequence = (array, value) => array.reduce(
(promiseChain, currentFunction) => promiseChain.then(currentFunction),
Promise.resolve(value)
)
runPromiseInSequence
Метод будет вызываться массивом, который возвращает промис для каждого элемента, и каждый промис в массиве будет выполняться по очереди.Пожалуйста, поймите внимательно. Если вы находите это неясным, вы можете обратиться к примеру:
const f1 = () => new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p1 running')
resolve(1)
}, 1000)
})
const f2 = () => new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p2 running')
resolve(2)
}, 1000)
})
const array = [f1, f2]
const runPromiseInSequence = (array, value) => array.reduce(
(promiseChain, currentFunction) => promiseChain.then(currentFunction),
Promise.resolve(value)
)
runPromiseInSequence(array, 'init')
Результат выполнения следующий:
уменьшить трубы
reduce
другойтипичное приложениеВы можете обратиться к функциональному методуpipe
Реализация:pipe(f, g, h)
это каррированная функция, которая возвращает новую функцию, которая завершит(...args) => h(g(f(...args)))
вызов. которыйpipe
Функция, возвращаемая методом, получит параметр, который передается вpipe
Первый параметр метода для его вызова.
const pipe = (...functions) => input => functions.reduce(
(acc, fn) => fn(acc),
input
)
Опыт тщательноrunPromiseInSequence
а такжеpipe
эти два метода, ониreduce
Типовые сценарии приложений.
реализоватьreduce
Так как же нам добитьсяreduce
Шерстяная ткань? Обратитесь к полифиллу из MDN:
if (!Array.prototype.reduce) {
Object.defineProperty(Array.prototype, 'reduce', {
value: function(callback /*, initialValue*/) {
if (this === null) {
throw new TypeError( 'Array.prototype.reduce ' +
'called on null or undefined' )
}
if (typeof callback !== 'function') {
throw new TypeError( callback +
' is not a function')
}
var o = Object(this)
var len = o.length >>> 0
var k = 0
var value
if (arguments.length >= 2) {
value = arguments[1]
} else {
while (k < len && !(k in o)) {
k++
}
if (k >= len) {
throw new TypeError( 'Reduce of empty array ' +
'with no initial value' )
}
value = o[k++]
}
while (k < len) {
if (k in o) {
value = callback(value, o[k], k, o)
}
k++
}
return value
}
})
}
используется в приведенном выше кодеvalue
в качестве начального значения и передатьwhile
петля, накапливаться и рассчитать в свою очередьvalue
результат и вывод. Но по сравнению с приведенной выше реализацией MDN, моя личная предпочтительная реализация:
Array.prototype.reduce = Array.prototype.reduce || function(func, initialValue) {
var arr = this
var base = typeof initialValue === 'undefined' ? arr[0] : initialValue
var startPoint = typeof initialValue === 'undefined' ? 1 : 0
arr.slice(startPoint)
.forEach(function(val, index) {
base = func(base, val, index + startPoint, arr)
})
return base
}
Основной принцип заключается в использованииforEach
заменитьwhile
Реализовано накопление результатов, они по сути одинаковые.
Я также посмотрел на pollyfill в ES5-shim, который точно такой же, как и в приведенной выше идее. Единственное отличие: я использовалforEach
Итеративный, в то время как ES5-shim использует простойfor
цикл. На самом деле, если бы мы были немного более сообразительными, мы бы указали, что массивforEach
Методы также являются новыми для ES5. Поэтому, используя один из API ES5 (forEach
), чтобы реализовать еще один API ES5 (reduce
), что на самом деле не имеет смысла — pollyfill здесь — смоделированная схема отката на случай несовместимости с ES5. Здесь не так много нужно исследовать, потому что основная цель состоит в том, чтобы надеяться, что читателиreduce
Имейте полное понимание.
Знать сокращение только через исходный код модуля Koa
Понимая и осознаваяreduce
метод, у нас уже есть более глубокое понимание этого. Наконец, давайте посмотрим на другойreduce
Пример использования — через исходный код KoaonlyМодуль, углубить впечатление:
var o = {
a: 'a',
b: 'b',
c: 'c'
}
only(o, ['a','b']) // {a: 'a', b: 'b'}
Этот метод возвращает новый объект с указанными свойствами фильтра. только реализация модуля:
var only = function(obj, keys){
obj = obj || {}
if ('string' == typeof keys) keys = keys.split(/ +/)
return keys.reduce(function(ret, key) {
if (null == obj[key]) return ret
ret[key] = obj[key]
return ret
}, {})
}
крошечныйreduce
Есть много мест, которые стоит обдумать и изучить в отношении его производных сцен. Чтобы сделать выводы из других фактов, жизненное обучение и применение являются ключом к технологическому прогрессу.
Несколько схем, реализованных композицией
Функциональная концепция - теперь эта древняя концепция в области фронтальной "везде". Функциональные идеи стоят обучения, одна деталь: составляют из-за его гениальной конструкции широко используются. Для его реализации, от ориентированного на процесс стиля для достижения функциональных, разных стилей, стоит изучить. Среди интервью интервьюер также часто требуется для достиженияcompose
метод, давайте посмотрим, чтоcompose
.
compose
На самом деле, как упоминалось ранееpipe
То же самое — выполнить ряд задач (методов) неопределенной длины, таких как:
let funcs = [fn1, fn2, fn3, fn4]
let composeFunc = compose(...funcs)
воплощать в жизнь:
composeFunc(args)
эквивалентно:
fn1(fn2(fn3(fn4(args))))
В заключениеcompose
Ключевые моменты метода:
-
compose
Аргумент представляет собой массив функций, и возвращаемое значение также является функцией. -
compose
Параметры могут быть любой длины, все параметры являются функциями, а направление выполнения — справа налево, поэтому исходная функция должна располагаться справа от параметров. -
compose
Функция, возвращаемая после выполнения, может получать параметры, и этот параметр будет использоваться как параметр исходной функции, поэтому параметры исходной функции являются многомерными, и возвращаемый результат исходной функции будет использоваться как параметр следующей функция и так далее. Поэтому, кроме исходной функции, полученное значение остальных функций унарно.
Мы обнаружили, что на самом делеcompose
а такжеpipe
Разница только в порядке вызова:
// compose
fn1(fn2(fn3(fn4(args))))
// pipe
fn4(fn3(fn2(fn1(args))))
Теперь, как мы ранее достиглиpipe
Методы те же, так что еще анализировать? Читайте дальше, чтобы узнать, какие еще цветы можно разыграть.
compose
Самая простая реализация ориентирована на процедуры:
const compose = function(...args) {
let length = args.length
let count = length - 1
let result
return function f1 (...arg1) {
result = args[count].apply(this, arg1)
if (count <= 0) {
count = length - 1
return result
}
count--
return f1.call(null, result)
}
}
Здесь главное использоватьЗакрытие, используйте переменную закрытия для сохранения результатаresult
И длина массива функций и индекс обхода, и использование рекурсивной идеи для накопления результатов. Общая реализация соответствует обычному процессуально-ориентированному мышлению и несложна для понимания.
Умные учащиеся могут также понять, что использование вышеупомянутогоreduce
метод, должен уметьфункциональныйрешить проблему:
const reduceFunc = (f, g) => (...arg) => g.call(this, f.apply(this, arg))
const compose = (...args) => args.reverse().reduce(reduceFunc, args.shift())
Благодаря предыдущему исследованию в сочетании сcall
,apply
метод, такую реализацию нетрудно понять.
Мы продолжаем развивать идеи, «поскольку это включает конкатенацию и управление потоком», то мы также можем использовать Promise для достижения:
const compose = (...args) => {
let init = args.pop()
return (...arg) =>
args.reverse().reduce((sequence, func) =>
sequence.then(result => func.call(null, result))
, Promise.resolve(init.apply(null, arg)))
}
В этой реализации используется функция Promise: сначалаPromise.resolve(init.apply(null, arg))
начать логику, начатьresolve
Значение — это возвращаемое значение после того, как последняя функция получает параметр, и функции выполняются последовательно. потому чтоpromise.then()
по-прежнему возвращает значение Promise, поэтомуreduce
Он может быть выполнен полностью в соответствии с экземпляром Promise.
Поскольку это можно реализовать с помощью Promise, тоgeneratorКонечно, это должно быть возможно. Вот вопрос для вас, чтобы подумать. Заинтересованные студенты могут попробовать его. Добро пожаловать, чтобы обсудить в области комментариев.
Наконец, давайте взглянем на реализацию хорошо известного в сообществе lodash и Redux.
лодаш версия
// lodash 版本
var compose = function(funcs) {
var length = funcs.length
var index = length
while (index--) {
if (typeof funcs[index] !== 'function') {
throw new TypeError('Expected a function');
}
}
return function(...args) {
var index = 0
var result = length ? funcs.reverse()[index].apply(this, args) : args[0]
while (++index < length) {
result = funcs[index].call(this, result)
}
return result
}
}
Версия lodash больше похожа на нашу первую реализацию и проще для понимания.
Редукс-версия
// Redux 版本
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
Короче говоря, по-прежнему в полной мере использовать массивreduce
метод.
Функциональные концепции действительно несколько абстрактны и требуют, чтобы разработчики обдумывали и отлаживали их вручную. Как только у вас появится прозрение, вы обязательно почувствуете в нем элегантность и простоту.
Расширенная реализация применения и привязки
интервью оthis
Связанные с привязкой темы сейчас "захлынули", и в то же времяbind
Реализация метода также обсуждается в сообществе. Но многое содержание еще не систематизировано, и есть некоторые недочеты. Вот краткий отрывок из статьи, которую я написал в начале 2017 года.От вопроса на собеседовании до «Возможно, я видел поддельный исходный код»для продвижения обсуждения. В "Все в одной сети"this
«На уроке мы познакомилиbind
реализация, здесь мы расширяемся дальше.
Я не буду вдаваться в подробности здесьbind
Читатели, не разбирающиеся в использовании функций, могут самостоятельно дополнить базовые знания. Давайте сначала посмотрим на предварительную версию реализации:
Function.prototype.bind = Function.prototype.bind || function (context) {
var me = this;
var argsArray = Array.prototype.slice.call(arguments);
return function () {
return me.apply(context, argsArray.slice(1))
}
}
Это ответ общеквалифицированного разработчика, если интервьюируемый может написать сюда, дайте ему 60 баллов.
Кратко прочитайте:
Основной принцип использованияapply
имитироватьbind
. внутри функцииthis
просто нужно связатьthis
функция или исходная функция. последнее использованиеapply
сделать параметры(context
) связать и вернуть.
При этом измените первый параметр (context
), в качестве параметров по умолчанию, предоставляемых исходной функции, которая также является основой для базового «каррирования».
В приведенной выше реализации список параметров, который мы возвращаем, содержит:argsArray.slice(1)
,Проблема в том, что функция предустановленного параметра теряется.
Представьте, что в функции привязки мы возвращаемся, если мы хотим реализовать предустановленные параметры (например,bind
достигнуто), столкнувшись с неловкой ситуацией. «Идеальный способ» на самом деле «карри»:
Function.prototype.bind = Function.prototype.bind || function (context) {
var me = this;
var args = Array.prototype.slice.call(arguments, 1);
return function () {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return me.apply(context, finalArgs);
}
}
Но продолжая исследовать, отметимbind
В методе:bind
Если возвращаемая функция используется как конструктор, она сопоставляется сnew
Если появляется ключевое слово, наша привязкаthis
нужно "не обращать внимания"this
Для привязки к экземпляру. То есть,new
оператор выше, чемbind
привязка, реализация, совместимая с этим случаем:
Function.prototype.bind = Function.prototype.bind || function (context) {
var me = this;
var args = Array.prototype.slice.call(arguments, 1);
var F = function () {};
F.prototype = this.prototype;
var bound = function () {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return me.apply(this instanceof F ? this : context || this, finalArgs);
}
bound.prototype = new F();
return bound;
}
Если вы думаете, что это конец, я на самом деле скажу вам, что оргазм вот-вот произойдет. Раньше я думал, что описанный выше метод идеален, пока не посмотрел исходный код es5-shim (который был соответствующим образом удален):
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 = target.apply(
this,
array_concat.call(args, array_slice.call(arguments))
);
if ($Object(result) === result) {
return result;
}
return this;
} else {
return target.apply(
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);
}
bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);
if (target.prototype) {
Empty.prototype = target.prototype;
bound.prototype = new Empty();
Empty.prototype = null;
}
return bound;
}
Какого черта делает реализация es5-shim? Вы можете не знать, на самом деле каждая функция имеетlength
Атрибуты. Да, так же, как массивы и строки. функциональныйlength
Атрибут, используемый для представления количества формальных параметров функции. Что еще более важно, функционалlength
Значения свойств не переопределяются. Я написал тестовый код для демонстрации:
function test (){}
test.length // 输出 0
test.hasOwnProperty('length') // 输出 true
Object.getOwnPropertyDescriptor('test', 'length')
// 输出:
// configurable: false,
// enumerable: false,
// value: 4,
// writable: false
Сказав это, это легко объяснить: **es5-shim для максимальной совместимости, в том числе для возврата функцийlength
Восстановление свойств. **И если так, как мы это реализовали ранее,length
Значение всегда равно нулю. Следовательно, поскольку он не может быть измененlength
Значение свойства , то всегда можно присвоить значение при инициализации! Так что мы можем пройтиeval
а такжеnew Function
способ динамического определения функций. Но из соображений безопасности в некоторых браузерах используетсяeval
илиFunction()
Конструкторы выбрасывают исключения. Однако по совпадению эти несовместимые браузеры в основном реализуютbind
функция, эти исключения не будут запущены. В приведенном выше коде сбросьте функцию привязкиlength
Атрибуты:
var boundLength = max(0, target.length - args.length)
Ситуация вызова конструктора, вbinder
Также действительно совместим с:
if (this instanceof bound) {
... // 构造函数调用情况
} else {
... // 正常方式调用
}
if (target.prototype) {
Empty.prototype = target.prototype;
bound.prototype = new Empty();
// 进行垃圾回收清理
Empty.prototype = null;
}
Сравнивая несколько версий реализации полифилла, дляbind
Должен иметь более глубокое понимание. Эта серия реализаций эффективно исследует очень важные точки знаний: такие какthis
Указание на закрытие JavaScript, прототип и цепочка прототипов, граничный случай программы проектирования и опыт рассмотрения совместимости и другие важные качества.
####Лучший вопрос для интервью
Наконец, во многих интервью в наши дни интервьюерbind
«В качестве темы. ** Если бы это был я, я мог бы сейчас избежать этой простой темы «сдачи теста», но проявите изобретательность и позвольте интервьюеру добиться «позвонить / подать заявку». ** Мы часто используемcall
/apply
Реализация моделированияbind
, при этом непосредственно реализуяcall
/apply
Это также просто:
Function.prototype.applyFn = function (targetObject, argsArray) {
if(typeof argsArray === 'undefined' || argsArray === null) {
argsArray = []
}
if(typeof targetObject === 'undefined' || targetObject === null){
targetObject = this
}
targetObject = new Object(targetObject)
const targetFnKey = 'targetFnKey'
targetObject[targetFnKey] = this
const result = targetObject[targetFnKey](...argsArray)
delete targetObject[targetFnKey]
return result
}
Такой код несложно понять, тело функцииthis
указывает на вызовapplyFn
Функция. Чтобы тело функцииthis
связаны вtargetObject
Выше мы используем метод неявной привязки:targetObject[targetFnKey](...argsArray)
.
Внимательные читатели обнаружат, что здесь есть проблема: еслиtargetObject
сам объект существуетtargetFnKey
Такие атрибуты используютсяapplyFn
функция, оригиналtargetFnKey
Значение свойства перезаписывается, а затем удаляется. Решение может использовать ES6Sybmol()
для обеспечения уникальных ключей; другим решением является использованиеMath.random()
Чтобы получить уникальный ключ, мы не будем повторять их здесь.
Последствия внедрения этих API
Реализация этих API не сложна, но она может должным образом протестировать основы JavaScript разработчика. Основа — это основа, ключ к более глубокому изучению контента и самая важная ссылка на пути к продвижению, которая требует внимания от каждого разработчика. Сегодня, с быстрым развитием и итерацией фронтенд-технологий, в бурной среде различных мнений, таких как «насыщен ли рынок фронтенда», «поиск работы фронтенда чрезвычайно горяч», «фронт-энд». конечный вход прост, а многие люди глупы», особенно важно развитие основных внутренних навыков. Это также является ключом к тому, как далеко и как долго вы можете зайти на переднем крае.
С точки зрения интервью, сталкивающиеся вопросы в конечном анализе является экспертиза Фонда, и только на основании переопределения в груди, чтобы иметь основные условия, чтобы разбить интервью.
обмен акциями
Эта статья из моего курса:Продвинутые базовые знания в области фронтенд-разработкиОдна из основных глав.
Заинтересованные читатели могут:
Нажмите на сторону ПК, чтобы узнать больше «Основные знания для расширенной разработки интерфейсов».
Мобильный Нажмите, чтобы узнать больше:
Краткое содержание:
Happy coding!