Эта серия будет посвящена интервью точки зренияJavaScript
,Node.js
(npm
пакет) и фреймворк для обобщения общих реализаций моделирования. Конкретный исходный код находится вгитхаб проектвкл., долгосрочные обновления и техническое обслуживание
Дедупликация массива
Самый примитивный метод дедупликации (одномерного) массива состоит в использовании двухуровневого цикла, который зацикливает исходный массив и новый массив соответственно; или мы можем использоватьindexOf
чтобы упростить внутренний цикл; или вы можете отсортировать исходный массив, а затем дедуплицировать его, что сократит один цикл, просто сравните два числа до и после; конечно, мы можем использоватьES5
,ES6
метод для упрощения написания метода дедупликации, например, мы можем использоватьfilter
чтобы упростить внутренний цикл или использоватьSet
,Map
, оператор расширения Это более простые в использовании методы, но их эффективность не должна быть выше, чем у оригинального метода. Дедупликация двумерного массива может быть основана на вышеописанном методе, чтобы определить, является ли элемент массивом, и если да, то выполняется рекурсивная обработка.
двойной цикл
var array = [1, 1, '1', '1'];
function unique(array) {
var res = [];
for (var i = 0, arrayLen = array.length; i < arrayLen; i++) {
for (var j = 0, resLen = res.length; j < resLen; j++ ) {
if (array[i] === res[j]) {
break;
}
}
if (j === resLen) {
res.push(array[i])
}
}
return res;
}
console.log(unique(array)); // [1, "1"]
Использовать indexOf
var array = [1, 1, '1'];
function unique(array) {
var res = [];
for (var i = 0, len = array.length; i < len; i++) {
var current = array[i];
if (res.indexOf(current) === -1) {
res.push(current)
}
}
return res;
}
console.log(unique(array));
Дедупликация после сортировки
var array = [1, 1, '1'];
function unique(array) {
var res = [];
var sortedArray = array.concat().sort();
var seen;
for (var i = 0, len = sortedArray.length; i < len; i++) {
// 如果是第一个元素或者相邻的元素不相同
if (!i || seen !== sortedArray[i]) {
res.push(sortedArray[i])
}
seen = sortedArray[i];
}
return res;
}
console.log(unique(array));
filter
filter
Может использоваться для упрощения внешнего цикла
Используйте индексОф:
var array = [1, 2, 1, 1, '1'];
function unique(array) {
var res = array.filter(function(item, index, array){
return array.indexOf(item) === index;
})
return res;
}
console.log(unique(array));
Сортировка для удаления дубликатов:
var array = [1, 2, 1, 1, '1'];
function unique(array) {
return array.concat().sort().filter(function(item, index, array){
return !index || item !== array[index - 1]
})
}
console.log(unique(array));
подход ES6
Задавать:
var array = [1, 2, 1, 1, '1'];
function unique(array) {
return Array.from(new Set(array));
}
console.log(unique(array)); // [1, 2, "1"]
снова упростить
function unique(array) {
return [...new Set(array)];
}
//或者
var unique = (a) => [...new Set(a)]
Карта:
function unique (arr) {
const seen = new Map()
return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
}
Типовое суждение
При оценке типа необходимо обратить внимание на следующие моменты
-
typeof
Для шести основных типов данныхUndefined
,Null
,Boolean
,Number
,String
,Object
(верхний регистр) возвращает результатundefined
,object
,boolean
,number
,string
,object
(нижний регистр), вы можете видетьNull
а такжеObject
типы возвращаютсяobject
нить;typeof
Но он может определить тип функции;Подводить итоги,typeof
Может обнаружить шесть типов, но не может обнаружитьnull
тип иObject
Типы подразделений, такие какArray
,Function
,Date
,RegExp
,Error
Ждать. -
Object.prototype.toString
очень мощный, он может определять основные типы данных иObject
тип подразделения под, даже как
Math
,JSON
,arguments
Он может определить их конкретный тип, он возвращает форму результата, например[object Number]
(обратите внимание, что последний тип данных в верхнем регистре).так,Object.prototype.toString
В принципе, все типы могут быть обнаружены, но иногда нужно учитывать совместимость низкоуровневых браузеров.
Общий API
// 该类型判断函数可以判断六种基本数据类型以及Boolean Number String Function Array Date RegExp Object Error,
// 其他类型因为遇到类型判断的情况较少所以都会返回object,不在进行详细的判断
// 比如ES6新增的Symbol,Map,Set等类型
var classtype = {};
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item) {
classtype["[object " + item + "]"] = item.toLowerCase();
})
function type(obj) {
// 解决IE6中null和undefined会被Object.prototype.toString识别成[object Object]
if (obj == null) {
return obj + "";
}
//如果是typeof后类型为object下的细分类型(Array,Function,Date,RegExp,Error)或者是Object类型,则要利用Object.prototype.toString
//由于ES6新增的Symbol,Map,Set等类型不在classtype列表中,所以使用type函数,返回的结果会是object
return typeof obj === "object" || typeof obj === "function" ?
classtype[Object.prototype.toString.call(obj)] || "object" :
typeof obj;
}
Оценка пустых объектов
чтобы определить, есть ли атрибут,for
Как только цикл выполнен, он указывает, что есть атрибут, и возвращается в это время.false
function isEmptyObject( obj ) {
var name;
for ( name in obj ) {
return false;
}
return true;
}
console.log(isEmptyObject({})); // true
console.log(isEmptyObject([])); // true
console.log(isEmptyObject(null)); // true
console.log(isEmptyObject(undefined)); // true
console.log(isEmptyObject(1)); // true
console.log(isEmptyObject('')); // true
console.log(isEmptyObject(true)); // true
мы можем видетьisEmptyObject
На самом деле судят не только пустые объекты. Но с тех порjQuery
Написано так, наверное, потому, что учитывая реальное развитиеisEmptyObject
Достаточно судить {} и {a: 1}. Если действительно только судить {}, то можно совместить с тем, что было написано в предыдущей статье.type
Функция отфильтровывает неподходящие случаи.
Определить объект окна
Window
объект имеетwindow
Атрибут указывает сам на себя, и по этому признаку можно определить, является ли онWindow
объект
function isWindow( obj ) {
return obj != null && obj === obj.window;
}
Массив суждений
isArray
Это встроенная функция оценки типа данных типа массива, но будут проблемы с совместимостью.polyfill
следующим образом
isArray = Array.isArray || function(array){
return Object.prototype.toString.call(array) === '[object Array]';
}
Массив классов суждения
jquery
осуществленныйisArrayLike
, возвращаются как массив, так и массивtrue
. так что еслиisArrayLike
вернутьtrue
, должно выполняться хотя бы одно из трех условий:
-
это массив
-
длина 0 Например, в следующем случае, если мы удалим оценку длины === 0, будет напечатано
false
, но мы все знаемarguments
это массивоподобный объект, вот что должно быть возвращеноtrue
изfunction a(){ console.log(isArrayLike(arguments)) } a();
-
lengths
свойство является числовым типом больше 0, иobj[length - 1]
должен существовать (учитывая случай arr = [3])
function isArrayLike(obj) {
// obj 必须有 length属性
var length = !!obj && "length" in obj && obj.length;
var typeRes = type(obj);
// 排除掉函数和 Window 对象
if (typeRes === "function" || isWindow(obj)) {
return false;
}
return typeRes === "array" || length === 0 ||
typeof length === "number" && length > 0 && (length - 1) in obj;
}
Судейство NaN
определить, является ли числоNaN
Вы не можете просто использовать === для оценки, потому чтоNaN
не равно никакому числу, в том числе и самому себе, обратите внимание, что вES6
изisNaN
Используется только значение в типе значенияNaN
вернусьtrue
isNaN: function(value){
return isNumber(value) && isNaN(value);
}
Определение элементов DOM
использоватьDOM
конкретный объектnodeType
Атрибуты(
isElement: function(obj){
return !!(obj && obj.nodeType === 1);
// 两次感叹号将值转化为布尔值
}
Объект оценки аргументов
в браузерах младших версийargument
объект черезObject.prototype.toString
Возвращенный после суждения[object Object]
, поэтому он должен быть совместим
isArguments: function(obj){
return Object.prototype.toString.call(obj) === '[object Arguments]' || (obj != null && Object.hasOwnProperty.call(obj, 'callee'));
}
глубокая копия
Если это массив, реализуется неглубокая копия, что лучше, чемslice
,concat
Возвращает свойства нового массива для реализации; реализация глубокой копии может использовать преимущества用JSON.parse
а такжеJSON.stringify
Чтобы добиться этого, но есть проблема, функция не может быть скопирована (массив, возвращаемый после копирования,null
). Все вышеперечисленные методы — это навыки.Давайте рассмотрим, как реализовать глубокую и поверхностную копию объекта или массива.
Поверхностная копия копирует только один слой, чего можно добиться с помощью Object.assign().Обратите внимание, что когда целевой объект имеет только один слой, это глубокая копия, методы массива concat(), slice() и т. д. Глубокая копия — это полная копия, многослойная копия, методы реализации включают библиотеку loadash, метод расширения jQuery, JSON.parse (JSON.stringify()) или рукописный рекурсивный метод.
мелкая копия
Идея очень проста, можно обойти объект, а затем поместить атрибуты и значения атрибутов в новый объект.
var shallowCopy = function(obj) {
// 只拷贝对象
if (typeof obj !== 'object') return;
// 根据obj的类型判断是新建一个数组还是对象
var newObj = obj instanceof Array ? [] : {};
// 遍历obj,并且判断是obj的属性才拷贝
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
глубокая копия
Идея также очень проста, то есть судить о типе значения атрибута при копировании, если это объект, можно рекурсивно вызывать функцию глубокого и поверхностного копирования.
var deepCopy = function(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
Плоский
рекурсия
Прокрутите элементы массива, и если это все еще массив, вызовите этот метод рекурсивно
// 方法 1
var arr = [1, [2, [3, 4]]];
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
}
else {
result.push(arr[i])
}
}
return result;
}
console.log(flatten(arr))
toString()
Если элементы массива все числа, вы можете использовать этот метод
// 方法2
var arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.toString().split(',').map(function(item){
return +item // +会使字符串发生类型转换
})
}
console.log(flatten(arr))
reduce()
// 方法3
var arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.reduce(function(prev, next){
return prev.concat(Array.isArray(next) ? flatten(next) : next)
}, [])
}
console.log(flatten(arr))
...
// 扁平化一维数组
var arr = [1, [2, [3, 4]]];
console.log([].concat(...arr)); // [1, 2, [3, 4]]
// 可以扁平化多维数组
var arr = [1, [2, [3, 4]]];
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console.log(flatten(arr))
карри
Универсальная версия
function curry(fn, args) {
var length = fn.length;
var args = args || [];
return function(){
newArgs = args.concat(Array.prototype.slice.call(arguments));
if (newArgs.length < length) {
return curry.call(this,fn,newArgs);
}else{
return fn.apply(this,newArgs);
}
}
}
function multiFn(a, b, c) {
return a * b * c;
}
var multi = curry(multiFn);
multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);
версия ES6
const curry = (fn, arr = []) => (...args) => (
arg => arg.length === fn.length
? fn(...arg)
: curry(fn, arg)
)([...arr, ...args])
let curryTest=curry((a,b,c,d)=>a+b+c+d)
curryTest(1,2,3)(4) //返回10
curryTest(1,2)(4)(3) //返回10
curryTest(1,2)(3,4) //返回10
Защита от тряски и дросселирования
Стабилизатор
function debounce(fn, wait) {
var timeout = null;
return function() {
if(timeout !== null)
{
clearTimeout(timeout);
}
timeout = setTimeout(fn, wait);
}
}
// 处理函数
function handle() {
console.log(Math.random());
}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));
дросселирование
использовать отметку времени
var throttle = function(func, delay) {
var prev = 0;
return function() {
var context = this;
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
Реализовано с таймером
var throttle = function(func, delay) {
var timer = null;
return function() {
var context = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function() {
func.apply(context, args);
timer = null;
}, delay);
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
Использовать отметку времени + таймер
При регулировании можно использовать временные метки или таймеры. Точнее, отметку времени + таймер можно использовать для немедленного выполнения обработчика события при первом запуске события, а также для выполнения обработчика события один раз после того, как событие было запущено в последний раз.
var throttle = function(func, delay) {
var timer = null;
var startTime = 0;
return function() {
var curTime = Date.now();
var remaining = delay - (curTime - startTime);
var context = this;
var args = arguments;
clearTimeout(timer);
if (remaining <= 0) {
func.apply(context, args);
startTime = Date.now();
} else {
timer = setTimeout(func, remaining);
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
имитировать новый
-
new
Полученный экземпляр может быть доступенConstructor
свойства в , вы также можете получить доступ кConstructor.prototype
свойства в , первое может бытьapply
, последнее может быть достигнуто путем добавления экземпляраproto
свойство указывает на конструкторprototype
реализовать - Нам также нужно определить, является ли возвращаемое значение объектом. Если это объект, мы вернем объект. Если нет, мы вернем то, что должны вернуть.
function New(){
var obj=new Object();
//取出第一个参数,就是我们要传入的构造函数;此外因为shift会修改原数组,所以arguments会被去除第一个参数
Constructor=[].shift.call(arguments);
//将obj的原型指向构造函数,这样obj就可以访问到构造函数原型中的属性
obj._proto_=Constructor.prototype;
//使用apply改变构造函数this的指向到新建的对象,这样obj就可以访问到构造函数中的属性
var ret=Constructor.apply(obj,arguments);
//要返回obj
return typeof ret === 'object' ? ret:obj;
}
function Otaku(name,age){
this.name=name;
this.age=age;
this.habit='Games'
}
Otaku.prototype.sayYourName=function(){
console.log("I am" + this.name);
}
var person=objectFactory(Otaku,'Kevin','18')
console.log(person.name)//Kevin
console.log(person.habit)//Games
console.log(person.strength)//60
имитировать вызов
-
call()
Метод вызывает функцию или метод с указанным значением this и рядом указанных значений параметров. - Шаги симуляции: установить функцию как свойство объекта -> выполнить функцию -> удалить функцию
-
this
Можно передавать параметрыnull
, когдаnull
Когда это считаетсяwindow
- Функции могут иметь возвращаемые значения
Простая версия
var foo = {
value: 1,
bar: function() {
console.log(this.value)
}
}
foo.bar() // 1
Идеальная версия
Function.prototype.call2 = function(context) {
var context=context||window
context.fn = this;
let args = [...arguments].slice(1);
let result = context.fn(...args);
delete context.fn;
return result;
}
let foo = {
value: 1
}
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
//表示bar函数的执行环境是foo,即bar函数里面的this代表foo,this.value相当于foo.value,然后给bar函数传递两个参数
bar.call2(foo, 'black', '18') // black 18 1
имитировать применять
-
apply()
реализация иcall()
Похожие, но разные параметры формы
Function.prototype.apply2 = function(context = window) {
context.fn = this
let result;
// 判断是否有第二个参数
if(arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
имитировать привязку
Function.prototype.bind2=function(context){
var self=this
var args=Array.prototype.slice.call(arguments,1);
var fNOP=function(){};
var fBound=function(){
var bindArgs=Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindAt))
}
}
имитировать instanceof
function instanceOf(left,right) {
let proto = left.__proto__;
let prototype = right.prototype
while(true) {
if(proto === null) return false
if(proto === prototype) return true
proto = proto.__proto__;
}
}
Макет JSON.stringify
JSON.stringify(value[, replacer [, space]])
-
Boolean | Number| String
Типы автоматически преобразуются в соответствующие примитивные значения. -
undefined
, любую функцию иsymbol
, игнорируется (при наличии в значении свойства объекта, не являющегося массивом), или преобразуется вnull
(при наличии в массиве). -
Неперечислимые свойства игнорируются
-
Если значение свойства объекта косвенным образом указывает на сам объект (циклическая ссылка), свойство также игнорируется.
function jsonStringify(obj) {
let type = typeof obj;
if (type !== "object") {
if (/string|undefined|function/.test(type)) {
obj = '"' + obj + '"';
}
return String(obj);
} else {
let json = []
let arr = Array.isArray(obj)
for (let k in obj) {
let v = obj[k];
let type = typeof v;
if (/string|undefined|function/.test(type)) {
v = '"' + v + '"';
} else if (type === "object") {
v = jsonStringify(v);
}
json.push((arr ? "" : '"' + k + '":') + String(v));
}
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
}
}
jsonStringify({x : 5}) // "{"x":5}"
jsonStringify([1, "false", false]) // "[1,"false",false]"
jsonStringify({b: undefined}) // "{"b":"undefined"}"
Макет JSON.parse
JSON.parse(text[, reviver])
Используется для анализа строк JSON и создания значений или объектов JavaScript, описываемых строками. Предусмотрена дополнительная функция оживления для выполнения преобразований (операций) над результирующим объектом перед возвратом.
использовать оценку
function jsonParse(opt) {
return eval('(' + opt + ')');
}
jsonParse(jsonStringify({x : 5}))
// Object { x: 5}
jsonParse(jsonStringify([1, "false", false]))
// [1, "false", falsr]
jsonParse(jsonStringify({b: undefined}))
// Object { b: "undefined"}
Избегайте использования в ненужных ситуациях
eval
, eval() — опасная функция, код, который он выполняет, имеет права исполнителя. Если строковый код, который вы запускаете с помощью eval(), манипулируется и модифицируется злоумышленниками (людьми с плохими намерениями), вы можете в конечном итоге запустить вредоносный код на компьютере пользователя с разрешениями вашей веб-страницы/расширения.
Использовать новую функцию()
Function
а такжеeval
имеют одинаковые свойства строкового параметра,eval
а такжеFunction
Оба имеют функцию динамической компиляции js-кода, но в реальном программировании это не рекомендуется.
var func = new Function(arg1, arg2, ..., functionBody)
var jsonStr = '{ "age": 20, "name": "jack" }'
var json = (new Function('return ' + jsonStr))();
создать объект
Самый простой способ создать пользовательский объект — это создатьObject
, а затем добавляя к нему свойства и методы, ранние разработчики часто использовали этот шаблон для создания объектов, а более поздние литеральные методы объекта стали предпочтительным шаблоном для создания объектов. несмотря на то чтоobject构造函数
или对象字面量
Все методы можно использовать для создания объектов, но эти методы используют один и тот же интерфейс для создания множества объектов, что приводит к большому количеству повторяющегося кода. Для решения этой проблемы люди стали использовать различные шаблоны для создания объектов, среди которых обычно рекомендуются четыре метода, в том числе构造函数模式
,原型模式
,构造函数和原型组合模式
,动态原型模式
, другие способы, в том числе工厂模式
,寄生构造函数模式
,稳妥构造函数模式
Обычно используется меньше. Среди этих методов следует выделить наиболее используемые и рекомендуемые.Композиция и динамический прототип
Шаблоны конструктора и прототипа композиции
преимущество:
- Устранены недостатки режима прототипа для эталонных объектов.
- Устранен недостаток, заключающийся в том, что в режиме прототипа нет возможности передавать параметры.
- Устраняет недостаток шаблона конструктора, который не может совместно использовать методы.
function Person(name) {
this.name = name
this.friends = ['lilei']
}
Person.prototype.say = function() {
console.log(this.name)
}
var person1 = new Person('hanmeimei')
person1.say() //hanmeimei
Шаблон динамического прототипа
преимущество:
- Объект-прототип может быть изменен при первом вызове конструктора.
- Изменения могут быть отражены во всех экземплярах
function Person(name) {
this.name = name
// 检测say 是不是一个函数
// 实际上只在当前第一次时候没有创建的时候在原型上添加sayName方法
//因为构造函数执行时,里面的代码都会执行一遍,而原型有一个就行,不用每次都重复,所以仅在第一执行时生成一个原型,后面执行就不必在生成,所以就不会执行if包裹的函数,
//其次为什么不能再使用字面量的写法,我们都知道,使用构造函数其实是把new出来的对象作用域绑定在构造函数上,而字面量的写法,会重新生成一个新对象,就切断了两者的联系!
if(typeof this.say != 'function') {
Person.prototype.say = function(
alert(this.name)
}
}
наследовать
原型链继承
Мало того, что это принесет ссылочные дефекты, мы не сможем инициализировать унаследованные свойства для разных экземпляров;构造函数继承
Этот метод позволяет избежать дефекта наследования классов, но мы не можем получить общий метод родительского класса, то есть метод, связанный с прототипом;组合继承
Решает проблемы двух вышеуказанных методов, но дважды вызывает конструктор родительского класса;寄生组合式继承
Усиливающая часть заключается в сокращении избыточного вызова конструктора родительского класса на основе комбинированного наследования.Рекомендуется использовать наследование композиции, паразитную композицию и наследование расширений ES6.Рекомендуется использовать наследование ES6 непосредственно в реальном производстве.
наследование композиции
// 声明父类
function Animal(color) {
this.name = 'animal';
this.type = ['pig','cat'];
this.color = color;
}
// 添加共有方法
Animal.prototype.greet = function(sound) {
console.log(sound);
}
// 声明子类
function Dog(color) {
// 构造函数继承
Animal.apply(this, arguments);
}
// 类式继承
Dog.prototype = new Animal();
var dog = new Dog('白色');
var dog2 = new Dog('黑色');
dog.type.push('dog');
console.log(dog.color); // "白色"
console.log(dog.type); // ["pig", "cat", "dog"]
console.log(dog2.type); // ["pig", "cat"]
console.log(dog2.color); // "黑色"
dog.greet('汪汪'); // "汪汪"
Примечание: комбинированное наследование с использованием вышеуказанного метода приведет к тому, что конструктор родительского класса будет вызываться дважды.На самом деле, мы можем оптимизировать комбинированное наследование через Dog.prototype = Animal.prototype; Dog.prototype.constructor = Dog.Конечно, окончательный метод оптимизации представляет собой следующую паразитную комбинацию. Если вы хотите понять конкретную оптимизацию комбинированного наследования, вы можете обратиться кГлубокое понимание цепочки прототипов JavaScript и наследования
Наследование паразитарного состава
function Animal(color) {
this.color = color;
this.name = 'animal';
this.type = ['pig', 'cat'];
}
Animal.prototype.greet = function(sound) {
console.log(sound);
}
function Dog(color) {
Animal.apply(this, arguments);
this.name = 'dog';
}
/* 注意下面两行 */
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.getName = function() {
console.log(this.name);
}
var dog = new Dog('白色');
var dog2 = new Dog('黑色');
dog.type.push('dog');
console.log(dog.color); // "白色"
console.log(dog.type); // ["pig", "cat", "dog"]
console.log(dog2.type); // ["pig", "cat"]
console.log(dog2.color); // "黑色"
dog.greet('汪汪'); // "汪汪"
Неглубокая копия Object.create() работает как следующая функция:
function create(obj) {
function F() {};
F.prototype = obj;
return new F();
}
Следует отметить, что поскольку прототип Animal копируется и присваивается Dog.prototype,constructor
Свойства также были переопределены, поэтому мы исправим это:
Dog.prototype.constructor = Dog;
расширяет наследование
class Animal {
constructor(color) {
this.color = color;
}
greet(sound) {
console.log(sound);
}
}
class Dog extends Animal {
constructor(color) {
super(color);
this.color = color;
}
}
let dog = new Dog('黑色');
dog.greet('汪汪'); // "汪汪"
console.log(dog.color); // "黑色"
реализовать карту
Основные моменты: 1. Каковы параметры callback-функции и как работать с возвращаемым значением. 2. Не изменяйте исходный массив.
Array.prototype.MyMap = function(fn, context){
var arr = Array.prototype.slice.call(this);//由于是ES5所以就不用...展开符了
var mappedArr = [];
for (var i = 0; i < arr.length; i++ ){
mappedArr.push(fn.call(context, arr[i], i, this));
}
return mappedArr;
}
внедрить уменьшить
Основные моменты:
1. Как быть с не переданным начальным значением
2. Каковы параметры callback-функции и как работать с возвращаемым значением.
Array.prototype.myReduce = function(fn, initialValue) {
var arr = Array.prototype.slice.call(this);
var res, startIndex;
res = initialValue ? initialValue : arr[0];
startIndex = initialValue ? 0 : 1;
for(var i = startIndex; i < arr.length; i++) {
res = fn.call(null, res, arr[i], i, this);
}
return res;
}
Реализовать Object.create
function create(proto) {
function F() {};
F.prototype = proto;
F.prototype.constructor = F;
return new F();
}
имитация аякса
-
ajax
Процесс запроса: создатьXMLHttpRequest
Объект, подключение к серверу, отправка запроса, получение данных ответа - после создания
XMLHttpRequest
Экземпляры объектов имеют множество методов и свойств.-
open
Метод аналогичен инициализации и не инициирует реальный запрос;send
сторона отправляет запрос и принимает необязательный параметр - Когда метод запроса
post
, параметры тела запроса могут быть переданы; когда метод запросаget
можно опустить или передатьnull
; - не важно как
get
а такжеpost
, параметры должны пройтиencodeURIComponent
сплайсинг после кодирования
-
Универсальная версия
//对请求data进行格式化处理
function formateData(data) {
let arr = [];
for (let key in data) {
//避免有&,=,?字符,对这些字符进行序列化
arr.push(encodeURIComponent(key) + '=' + data[key])
}
return arr.join('&');
}
function ajax(params) {
//先对params进行处理,防止为空
params = params || {};
params.data = params.data || {};
//普通GET,POST请求
params.type = (params.type || 'GET').toUpperCase();
params.data = formateData(params.data);
//如果是在ie6浏览器,那么XMLHttoRequest是不存在的,应该调用ActiveXObject;
let xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
if (params.type === 'GET') {
xhr.open(params.type, params.url + '?' + params.data, true);
xhr.send();
} else {
xhr.open(params.type, params.url, true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
xhr.send(params.data);
}
// 这里有两种写法,第一种写法:当xhr.readyState===4的时候,会触发onload事件,直接通过onload事件 进行回调函数处理
xhr.onload = function () {
if (xhr.status === 200 || xhr.status === 304 || xhr.status === 206) {
var res;
if (params.success && params.success instanceof Function) {
res = JSON.parse(xhr.responseText);
params.success.call(xhr, res);
}
} else {
if (params.error && params.error instanceof Function) {
res = xhr.responseText;
params.error.call(xhr, res);
}
}
}
//第二种写法,当xhr.readyState===4时候,说明请求成功返回了,进行成功回调
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
// 进行onload里面的处理函数
}
}
}
обещанная версия
// 使用promise实现一个简单的ajax
/**
* 首先,可能会使用到的xhr方法或者说属性
* onloadstart // 开始发送时触发
* onloadend // 发送结束时触发,无论成功不成功
* onload // 得到响应
* onprogress // 从服务器上下载数据,每50ms触发一次
* onuploadprogress // 上传到服务器的回调
* onerror // 请求错误时触发
* onabort // 调用abort时候触发
* status // 返回状态码
* setRequestHeader // 设置请求头
* responseType // 请求传入的数据
*/
// 默认的ajax参数
let ajaxDefaultOptions = {
url: '#', // 请求地址,默认为空
method: 'GET', // 请求方式,默认为GET请求
async: true, // 请求同步还是异步,默认异步
timeout: 0, // 请求的超时时间
dataType: 'text', // 请求的数据格式,默认为text
data: null, // 请求的参数,默认为空
headers: {}, // 请求头,默认为空
onprogress: function () {}, // 从服务器下载数据的回调
onuploadprogress: function () {}, // 处理上传文件到服务器的回调
xhr: null // 允许函数外部创建xhr传入,但是必须不能是使用过的
};
function _ajax(paramOptions) {
let options = {};
for (const key in ajaxDefaultOptions) {
options[key] = ajaxDefaultOptions[key];
}
// 如果传入的是否异步与默认值相同,就使用默认值,否则使用传入的参数
options.async = paramOptions.async === ajaxDefaultOptions.async ? ajaxDefaultOptions.async : paramOptions.async;
// 判断传入的method是否为GET或者POST,否则传入GET 或者可将判断写在promise内部,reject出去
options.method = paramOptions.method ? ("GET" || "POST") : "GET";
// 如果外部传入xhr,否则创建一个
let xhr = options.xhr || new XMLHttpRequest();
// return promise对象
return new Promise(function (resolve, reject) {
xhr.open(options.method, options.url, options.async);
xhr.timeout = options.timeout;
// 设置请求头
for (const key in options.headers) {
xhr.setRequestHeader(key, options.headers[key]);
}
// 注册xhr对象事件
xhr.responseType = options.dataType;
xhr.onprogress = options.onprogress;
xhr.onuploadprogress = options.onuploadprogress;
// 开始注册事件
// 请求成功
xhr.onloadend = function () {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
resolve(xhr);
} else {
reject({
errorType: "status_error",
xhr: xhr
});
}
};
// 请求超时
xhr.ontimeout = function () {
reject({
errorType: "timeout_error",
xhr: xhr
});
}
// 请求错误
xhr.onerror = function () {
reject({
errorType: "onerror",
xhr: xhr
});
}
// abort错误(未明白,只知道是三种异常中的一种)
xhr.onabort = function () {
reject({
errorType: "onabort",
xhr: xhr
});
}
// 捕获异常
try {
xhr.send(options.data);
} catch (error) {
reject({
errorType: "send_error",
error: error
});
}
});
}
// 调用示例
_ajax({
url: 'http://localhost:3000/suc',
async: true,
onprogress: function (evt) {
console.log(evt.position / evt.total);
},
dataType: 'text/json'
}).then(
function (xhr) {
console.log(xhr.response);
},
function (e) {
console.log(JSON.stringify(e))
});
макет jsonp
// foo 函数将会被调用 传入后台返回的数据
function foo(data) {
console.log('通过jsonp获取后台数据:', data);
document.getElementById('data').innerHTML = data;
}
/**
* 通过手动创建一个 script 标签发送一个 get 请求
* 并利用浏览器对 <script> 不进行跨域限制的特性绕过跨域问题
*/
(function jsonp() {
let head = document.getElementsByTagName('head')[0]; // 获取head元素 把js放里面
let js = document.createElement('script');
js.src = 'http://domain:port/testJSONP?a=1&b=2&callback=foo'; // 设置请求地址
head.appendChild(js); // 这一步会发送请求
})();
// 后台代码
// 因为是通过 script 标签调用的 后台返回的相当于一个 js 文件
// 根据前端传入的 callback 的函数名直接调用该函数
// 返回的是 'foo(3)'
function testJSONP(callback, a, b) {
return `${callback}(${a + b})`;
}
Моделирование модели публикации-подписки
class Pubsub {
constructor() {
this.handles = {}
}
subscribe(type, handle) {
if (!this.handles[type]) {
this.handles[type] = []
}
this.handles[type].push(handle)
}
unsubscribe(type, handle) {
let pos = this.handles[type].indexOf(handle)
if (!handle) {
this.handles.length = 0
} else {
~pos && this.handles[type].splice(pos, 1)
}
}
publish() {
let type = Array.prototype.shift.call(arguments)
this.handles[type].forEach(handle => {
handle.apply(this, arguments)
})
}
}
const pub = new Pubsub()
pub.subscribe('a', function() {console.log('a', ...arguments)})
pub.publish('a', 1, 2, 3)
// a 1 2 3
Имитация setInterval с помощью setTimeout
существуетsetTimeout
Метод вызывается сноваsetTimeout
, вы можете достичь цели прерывистого вызова.
Так почему же рекомендуется использоватьsetTimeout
заменятьsetInterval
Шерстяная ткань?setTimeout
стиль прерывистых вызовов и традиционныйsetInterval
В чем разница между прерывистыми звонками?
Разница в том, чтоsetInterval
Прерывистый вызов начинает отсчет времени до выполнения предыдущего метода. Например, если прерывистое время составляет 500 мс, то последний метод будет помещен в последовательность выполнения независимо от того, выполнялся ли в это время предыдущий метод. В это время будет возникать проблема.Если время выполнения предыдущего метода превышает 500 мс, а добавление составляет 1000 мс, это означает, что после завершения выполнения предыдущего метода последний метод будет выполняться немедленно, потому что прерывистое время в это время превысило 500 мс.
«Интервальные вызовы (setInterval) редко используются в среде разработки, потому что последний, скорее всего, начнется до окончания предыдущего»
Простая версия
Recurns вызывает функцию settimeout
Предупреждение. Использование arguments.callee() запрещено в версии 5 ECMAScript (ES5) в строгом режиме. Избегайте использования arguments.callee(), когда функция должна вызывать сама себя, либо давая функциональному выражению имя, либо используя объявление функции.
var executeTimes = 0;
var intervalTime = 500;
//var intervalId = null;
setTimeout(timeOutFun,intervalTime);
function timeOutFun(){
executeTimes++;
console.log("doTimeOutFun——"+executeTimes);
if(executeTimes<5){
setTimeout(arguments.callee,intervalTime);
}
}
<!--// 放开下面的注释运行setInterval的Demo-->
<!--intervalId = setInterval(intervalFun,intervalTime);-->
<!--function intervalFun(){-->
<!-- executeTimes++;-->
<!-- console.log("doIntervalFun——"+executeTimes);-->
<!-- if(executeTimes==5){-->
<!-- clearInterval(intervalId);-->
<!-- }-->
<!--}-->
Расширенное издание
let timeMap = {}
let id = 0 // 简单实现id唯一
const mySetInterval = (cb, time) => {
let timeId = id // 将timeId赋予id
id++ // id 自增实现唯一id
let fn = () => {
cb()
timeMap[timeId] = setTimeout(() => {
fn()
}, time)
}
timeMap[timeId] = setTimeout(fn, time)
return timeId // 返回timeId
}
const myClearInterval = (id) => {
clearTimeout(timeMap[id]) // 通过timeMap[id]获取真正的id
delete timeMap[id]
}
Макетная реализация Promise
Реализация обещаний
-
Для реализации
then
метод, нам нужно учитывать асинхронный случай, то есть когдаresolve
существуетsetTimeout
выполнять внутри,then
Времяstate
ещеpending
государство, нам нужноthen
При вызове успех и неудача сохраняются в соответствующих массивах.reject
илиresolve
, просто позвони им -
Еще один момент, на который следует обратить внимание, это то, как реализовать
then
цепочка методов, мы по умолчанию используем первыйthen
метод возвращаетpromise
, метод указан в исходном коде, т.е.then
метод возвращает новыйpromise
, называетсяpromise2:promise2=new Promise((resolve,reject)=>{})
- положи это
promise2
Возвращаемое значение передается следующемуthen
середина - Если возвращается обычное значение, передать обычное значение следующему
then
середина
- положи это
-
resolvePromise
Реализация функции является ключевым моментом;promise
указано в спецификацииonFullfilled()
илиonRejected()
значение, то есть первоеthen
Возвращаемое значение называетсяx
,судитьx
функция называетсяresolvePromise
. В частности, прежде всего, чтобы увидеть, является ли xpromise
. еслиpromise
, затем примите его результат как новыйpromise2
Если успешным результатом является обычное значение, оно напрямую используется какpromise2
успешный результат, поэтому сравните x иpromise2
,resolvePromise
Параметрыpromise2
(возвращается по умолчаниюpromise
),x
(Мы самиreturn
Объект),resolve
,reject
;resolve
а такжеreject
даpromise2
из
class Promise{
constructor(executor){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
};
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled,onRejected) {
// onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// onRejected如果不是函数,就忽略onRejected,直接扔出错误
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise2 = new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
// 异步
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'rejected') {
// 异步
setTimeout(() => {
// 如果报错
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
// 异步
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
// 异步
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
// 返回promise,完成链式
return promise2;
}
}
function resolvePromise(promise2, x, resolve, reject){
// 循环引用报错
if(x === promise2){
// reject报错
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 防止多次调用
let called;
// x不是null 且x是对象或者函数
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
// A+规定,声明then = x的then方法
let then = x.then;
// 如果then是函数,就默认是promise了
if (typeof then === 'function') {
// 就让then执行 第一个参数是this 后面是成功的回调 和 失败的回调
then.call(x, y => {
// 成功和失败只能调用一个
if (called) return;
called = true;
// resolve的结果依旧是promise 那就继续解析
resolvePromise(promise2, y, resolve, reject);
}, err => {
// 成功和失败只能调用一个
if (called) return;
called = true;
reject(err);// 失败了就失败了
})
} else {
resolve(x); // 直接成功即可
}
} catch (e) {
// 也属于失败
if (called) return;
called = true;
// 取then出错了那就不要在继续执行了
reject(e);
}
} else {
resolve(x);
}
}
Реализация методов разрешения, отклонения, гонки и гонки
//reject方法
Promise.reject = function(val){
return new Promise((resolve,reject)=>{
reject(val)
});
}
//race方法
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
};
})
}
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
};
};
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
};
});
}
постскриптум:
Для некоторых конкретных реализаций кода перейдите кдомашняя страница проекта на гитхабе, если вы считаете, что это полезно для вас, сделайте форкэтот проект, передвигать кирпичи непросто, и более поздняя реализация исходного кода JavaScript, узла и фреймворка будетgithubОбновление, спасибо за чтение.
Внешняя маршрутизация
Хэш-способ
class Routers {
constructor() {
// 储存hash与callback键值对
this.routes = {};
// 当前hash
this.currentUrl = '';
// 记录出现过的hash
this.history = [];
// 作为指针,默认指向this.history的末尾,根据后退前进指向history中不同的hash
this.currentIndex = this.history.length - 1;
this.refresh = this.refresh.bind(this);
this.backOff = this.backOff.bind(this);
// 默认不是后退操作
this.isBack = false;
window.addEventListener('load', this.refresh, false);
window.addEventListener('hashchange', this.refresh, false);
}
route(path, callback) {
this.routes[path] = callback || function() {};
}
refresh() {
this.currentUrl = location.hash.slice(1) || '/';
if (!this.isBack) {
// 如果不是后退操作,且当前指针小于数组总长度,直接截取指针之前的部分储存下来
// 此操作来避免当点击后退按钮之后,再进行正常跳转,指针会停留在原地,而数组添加新hash路由
// 避免再次造成指针的不匹配,我们直接截取指针之前的数组
// 此操作同时与浏览器自带后退功能的行为保持一致
if (this.currentIndex < this.history.length - 1)
this.history = this.history.slice(0, this.currentIndex + 1);
this.history.push(this.currentUrl);
this.currentIndex++;
}
this.routes[this.currentUrl]();
console.log('指针:', this.currentIndex, 'history:', this.history);
this.isBack = false;
}
// 后退功能
backOff() {
// 后退操作设置为true
this.isBack = true;
this.currentIndex <= 0
? (this.currentIndex = 0)
: (this.currentIndex = this.currentIndex - 1);
location.hash = `#${this.history[this.currentIndex]}`;
this.routes[this.history[this.currentIndex]]();
}
}
Метод истории
class Routers {
constructor() {
// 储存hash与callback键值对
this.routes = {};
// 当前hash
this.currentUrl = '';
// 记录出现过的hash
this.history = [];
// 作为指针,默认指向this.history的末尾,根据后退前进指向history中不同的hash
this.currentIndex = this.history.length - 1;
this.refresh = this.refresh.bind(this);
this.backOff = this.backOff.bind(this);
// 默认不是后退操作
this.isBack = false;
window.addEventListener('load', this.refresh, false);
window.addEventListener('hashchange', this.refresh, false);
}
route(path, callback) {
this.routes[path] = callback || function() {};
}
refresh() {
this.currentUrl = location.hash.slice(1) || '/';
if (!this.isBack) {
// 如果不是后退操作,且当前指针小于数组总长度,直接截取指针之前的部分储存下来
// 此操作来避免当点击后退按钮之后,再进行正常跳转,指针会停留在原地,而数组添加新hash路由
// 避免再次造成指针的不匹配,我们直接截取指针之前的数组
// 此操作同时与浏览器自带后退功能的行为保持一致
if (this.currentIndex < this.history.length - 1)
this.history = this.history.slice(0, this.currentIndex + 1);
this.history.push(this.currentUrl);
this.currentIndex++;
}
this.routes[this.currentUrl]();
console.log('指针:', this.currentIndex, 'history:', this.history);
this.isBack = false;
}
// 后退功能
backOff() {
// 后退操作设置为true
this.isBack = true;
this.currentIndex <= 0
? (this.currentIndex = 0)
: (this.currentIndex = this.currentIndex - 1);
location.hash = `#${this.history[this.currentIndex]}`;
this.routes[this.history[this.currentIndex]]();
}
}
Ленивая загрузка изображения
<script>
// 获取所有的图片标签
const imgs = document.getElementsByTagName('img')
// 获取可视区域的高度
const viewHeight = window.innerHeight || document.documentElement.clientHeight
// num用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
let num = 0
function lazyload(){
for(let i=num; i<imgs.length; i++) {
// 用可视区域高度减去元素顶部距离可视区域顶部的高度
let distance = viewHeight - imgs[i].getBoundingClientRect().top
// 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出
if(distance >= 0 ){
// 给元素写入真实的src,展示图片
imgs[i].src = imgs[i].getAttribute('data-src')
// 前i张图片已经加载完毕,下次从第i+1张开始检查是否露出
num = i + 1
}
}
}
// 监听Scroll事件
window.addEventListener('scroll', lazyload, false);
</script>
Использованная литература: