предисловие
Цель этой статьи
из JS运行
,设计
,数据
,应用
Четыре угла, чтобы разобраться в основных точках знаний JS
Схема темы
-
JS запустить
- переменное продвижение
- контекст выполнения
- объем
- let
- цепочка прицелов
- Закрытие
- цикл событий
-
JS-дизайн
- прототип
- Сеть прототипов
- this
- call
- apply
- bind
- new
- наследовать
-
JS-данные
- тип данных
- Хранение данных (глубокое и поверхностное копирование)
- Оценка типа данных (неявное преобразование, равенство и равенство, два объекта равны)
- Манипуляции с данными (обход массива, обход объекта)
- Расчет данных (ошибка расчета)
-
JS-приложение
- Анти-шейк, дросселирование, каррирование
1. JS работает
условно разделить на четыре этапа
-
лексический анализ: Разделите строку в коде js на осмысленные блоки кода, называемые
词法单元
- Когда браузер просто получает файл JS или сегмент кода скрипта, он будет думать, что это длинная строка.
- Это непонятно, поэтому разбейте его на осмысленные фрагменты, например:
var a = 1
-
Разбор:Буду
词法单元
поток в抽象语法树(AST)
и обработайте сгенерированные узлы дерева AST,- Например, если используется синтаксис ES6, используются let и const, и их необходимо преобразовать в var.
Зачем вам нужно абстрактное синтаксическое дерево?
- Абстрактное синтаксическое дерево не зависит от конкретной грамматики, не зависит от деталей языка, в нем удобно делать множество операций
- С другой стороны, существует много языков, C, C++, Java, Javascript и т. д., и у них разные языковые спецификации.
- Однако после преобразования в абстрактное синтаксическое дерево оно становится непротиворечивым, что удобно для компилятора при выполнении дальнейших операций, таких как добавления, удаления, изменения и проверки.
-
Этап предварительного разбора:
- правила масштабирования будут определены
- Подъем переменных и функций
-
этап выполнения:
- Создайте контекст выполнения и сгенерируйте стек контекста выполнения
- Выполнить исполняемый код в соответствии с циклом событий
1 Область применения
Определяет область действия функций и переменных
- разделен на
全局作用域
а также函数作用域
, - В отличие от языков C и JAVA, JS не имеет области действия на уровне блоков, которая представляет собой просто область видимости фигурных скобок.
2. Подъем переменных и функций
Объявления глобальных переменных и функций подняты
- Есть три способа объявить функцию,
function foo() {}
var foo = function () {}
var foo = new Function()
- можно разделить на две категории,
直接创建
а также变量赋值
-
变量赋值函数
а также赋值普通变量
Приоритет по положению, имя переменной такое же, как и первое перезаписывается - Непосредственное создание функции имеет более высокий приоритет, чем присвоение переменной, то же самое имя занимает первое, которое не имеет ничего общего с позицией, то есть прямое создание функции имеет наивысший приоритет даже после объявления переменной. .
3. Контекст выполнения
Есть разные области видимости, разные среды исполнения, нам нужно управлять переменными этих контекстов
- Среда выполнения делится на три типа, контекст выполнения соответствует среде выполнения
- глобальная среда выполнения
- среда выполнения функции
- среда выполнения eval (проблемы с производительностью не упоминаются)
- глобальный контекст выполнения
- Сначала найдите объявление переменной,
- Найдите снова объявление функции
- контекст выполнения функции
- Сначала найдите параметры функции и объявления переменных
- присвоить фактический параметр формальному параметру
- найти объявление функции
- Когда несколько функций вложены, будет несколько контекстов выполнения, что требует
执行上下文栈
для технического обслуживания, последний вошел первым - Контекст выполнения содержит
变量环境
а также词法环境
-
变量环境
Он содержит переменные, которые можно использовать в текущей среде. - Текущая среда бесполезна, вот и все
作用域链
4. Цепочка областей
- Цитирование определения высоты JS:Цепочка областей действия для обеспечения упорядоченного доступа к переменным и функциям, к которым имеет доступ контекст выполнения.
- Порядок поиска переменных не в порядке стека контекста выполнения, а в порядке
词法作用域
решенный - Лексическая область
静态作用域
, который определяется позицией объявления функции, независимо от того, где вызывается функция, поэтому js такой особенный
5. Статическая и динамическая область видимости
- Лексическая область видимости определяется при написании кода или определении
- В то время как динамическая область определяется во время выполнения (
this也是!
)
var a = 2;
function foo() {
console.log(a); // 静态2 动态3
}
function bar() {
var a = 3;
foo();
}
bar();
Закрытие
- из-за
作用域
Ограничение , мы не можем получить доступ к переменным, определенным внутри функции, за пределами области действия функции, и фактические потребности требуют этого, что используется здесь闭包
- Цитирование определения JS Definitive Guide:Замыкание — это функция, которая имеет доступ к переменной в области видимости другой функции.
1. Функция закрытия
-
for循环遍历进行事件绑定
При выводе значения i это длина цикла for + 1 - Это не то, что нам нужно, потому что JS не имеет области действия на уровне блоков, значение i, определенное в var, не уничтожается и сохраняется в глобальной переменной окружения.
- Значение i, полученное при специальном выполнении события, является значением i после нескольких вычислений в глобальной переменной.
for(var i = 0;i < 3;i++){
document.getElementById(`item${i+1}`).onclick = function() {
console.log(i);//3,3,3
}
}
- Функция закрытия: внешняя функция была выполнена, переменные, на которые ссылается внутренняя функция внешней функции, все еще хранятся в памяти, и набор переменных может быть вызван
闭包
- В процессе компиляции для внутренней функции движок JS просканирует этот метод один раз.Если будет ссылка на переменную внешней функции, пространство кучи будет создано и заменено новым.
Closure
объект, используемый для хранения переменных закрытия - Используйте эту функцию, чтобы добавить уровень закрытия в метод для хранения текущего значения i и привязать событие к функции, возвращаемой вновь добавленной анонимной функцией.
for(var i = 0;i < 3;i++){
document.getElementById(`item${i+1}`).onclick = make(i);
}
function make(e) {
return function() {
console.log(e)//0,1,2
};
заключительная записка
- Мы непреднамеренно написали замыкание, и переменные, на которые внутренняя функция ссылается на внешнюю функцию, все еще хранятся в памяти,
- То, что должно быть уничтожено, не уничтожено,
由于疏忽或错误造成程序未能释放已经不再使用的内存
, в результате чего内存泄漏
- В то же время обратите внимание, что замыкание не вызовет утечек памяти, наше неправильное использование замыкания и есть утечка памяти.
цикл событий
- Выполнение кода JS на основе цикла событий
- JS — это один поток, который гарантирует, что выполнение не будет заблокировано асинхронным
- исполнительный механизм
- Проще говоря, один стек выполнения и две очереди задач
- Когда макрозадача найдена, она помещается в очередь макрозадач, а когда обнаруживается микрозадача, она помещается в очередь микрозадач.
- Когда стек выполнения пуст, выполните все микрозадачи в очереди микрозадач, а затем возьмите макрозадачу из очереди макрозадач для выполнения.
- так цикл
- Макросы и микрозадачи macroTask: setTimeout, setInterval, ввод-вывод, рендеринг пользовательского интерфейса микрозадача: обещание.затем
2. JS-дизайн
1. Прототип
- Дизайн JS
- Появились новые операторы и конструкторы, но нет понятия классов, а используются прототипы для имитации классов для достижения наследования.
- Путешествие по JS-дизайну
- В начале разработки JS дал короткое время и был определен как простой язык веб-скриптов, не слишком сложный, и хотел имитировать концепцию Java (именно поэтому JS называется JavaScript)
- Таким образом, заимствование из Java
对象
,构造函数
,new
концепция оператора, при этом отказываясь от сложногоclass
(класс) концепция
- механизм наследования
- нужен
继承
Механизм для связывания всех объектов вместе, вы можете использовать конструктор - Но недостатком конструктора для создания экземпляра объекта является то, что
无法共享属性和方法
-
prototype
Атрибуты
- Для решения вышеуказанных проблем введение
prototype
Собственность, то есть мы часто говорим, что прототип - установить один для конструктора
prototype
Атрибуты и методы, которые должны быть общими для объектов экземпляра, помещаются в этот объект.
После того, как весь базовый проект завершен, следующие API-интерфейсы являются логичными.
прототип
- Каждый объект js связан с другим объектом при его создании.
- Этот объект является прототипом, и каждый объект наследует свойства от прототипа.
proto
- Каждый объект имеет свойство __proto__, которое указывает на прототип объекта.
- Свойство прототипа конструктора равно свойству __proto__ экземпляра объекта.
- Это свойство не является каноническим свойством в ES5, это просто синтаксический сахар для удобства получения прототипов в браузере.
- мы можем использовать
Object.getPrototype()
метод получения прототипа
constructorПрототипы не указывают на экземпляры, потому что конструктор может иметь несколько экземпляров объекта. Но есть прототипы, указывающие на конструкторы, и каждый прототип имеет атрибут конструктора, указывающий на связанный с ним конструктор.
function Per() {} // 构造函数
const chi = new Per() // 实例对象
chi.__proto__ === Per.prototype // 获取对象的原型 也是就构造函数的prototype属性
Per.prototype.constructor === Per // constructor属性 获取当前原型关联的构造函数
Экземпляры и прототипы
- Когда атрибут экземпляра не может быть найден, он будет искать атрибут прототипа, связанного с объектом, и продолжит поиск.
- Эта цепочка отношений между экземпляром и архетипом, образующая
原型链
function Foo() {}
Foo.prototype.name = 'tom'
const foo = new Foo()
foo.name = 'Jerry'
console.log(foo.name); // Jerry
delete foo.name
console.log(foo.name); // tom
2. Цепочка прототипов
Сначала покажите знакомую карту сети
Это цепочка отношений между экземпляром и конструктором, прототипом
-
примерprotoуказать на прототип
-
Свойство прототипа конструктора указывает на прототип
-
Свойство конструктора прототипа указывает на конструктор
-
всех конструкторовprotoуказывает на Function.prototype
-
Function.prototype protoуказывает на Object.prototype
-
Object.prototype protoуказывает на ноль
Прототип функционального объекта (Function.prototype) — это машина, ответственная за создание конструктора, включая объект, строку, число, логическое значение, массив и функцию. Затем конструктор используется для создания конкретного экземпляра объекта.
function Foo() {}
// 1. 所有构造函数的 __proto__ 指向 Function.prototype
Foo.__proto__ // ƒ () { [native code] }
Function.__proto__ // ƒ () { [native code] }
Object.__proto__ // ƒ () { [native code] }
// 2. 所有构造函数原型和new Object创造出的实例 __proto__ 指向 Object.prototype
var o = new Object()
o.__proto__ // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ ...}
Function.prototype.__proto__ // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ ...}
// 3. Object.prototype 指向null
Object.prototype.__proto__ // null
2. this
- При вызове как объектный метод, указывающий на объект
obj.b();
// указывает на объект - Как вызов метода функции,
var b = obj.b; b();
// Указываем на глобальное окно (метод функции на самом деле является методом объекта окна, поэтому он такой же, как и выше, кто бы его ни вызывал, указывает на кого) - Вызывается как конструктор
var b = new Per();
// это указывает на текущий объект экземпляра - Звоните как звоните и подавайте заявку
obj.b.apply(object, []);
// это указывает на текущее указанное значение
кто бы ни звонил
const obj = {a: 1, f: function() {console.log(this, this.a)}}
obj.f(); // 1
const a = 2;
const win = obj.f;
win(); // 2
function Person() {
this.a = 3;
this.f = obj.f;
}
const per = new Person()
per.f() // 3
const app = { a: 4 }
obj.f.apply(app); // 4
this
указать да动态作用域
, кто кому звонит,
3. call
- Определение: вызов функции или метода с указанным значением this и рядом указанных значений параметров.
- Пример:
var obj = {
value: 1,
}
function foo (name, old) {
return {
value:this.value,
name,
old
}
}
foo.call(obj, 'tom', 12); // {value: 1, name: "tom", old: 12}
- Требовать:
- вызов меняет суть этого,
- функция foo выполняется
- Поддержка передачи параметров, количество параметров не фиксировано
- Этот параметр может быть передан null, при передаче null он указывает на окно
- Функции могут иметь возвращаемые значения
- Нефункциональная обработка оценки вызова
- реализация и идеи
Function.prototype.call1 = function (context, ...args) {
if(typeof this !== 'function') {throw new Error('Error')} // 非函数调用判断处理
context = context || window; // 非运算符判定传入参数 null则指向window
const key = Symbol(); // 利用symbol创建唯一的属性名,防止覆盖原有属性
context[key] = this; // 把函数作为对象的属性,函数的this就指向了对象
const result = context[key](...args) // 临时变量赋值为执行对象方法
delete context[key]; // 删除方法,防止污染对象
return result // return 临时变量
}
- Сценарии применения
Измените этот указатель, в основном, чтобы заимствовать методы или свойства.
-
- Определить тип данных, заимствовать
Object
изtoString
методObject.prorotype.toString.call()
- Определить тип данных, заимствовать
-
- Подкласс наследует свойства родительского класса,
function Chi() {Par.call(this)}
- Подкласс наследует свойства родительского класса,
4. apply
- Определение: вызов функции или метода с указанным значением this и массивом (массив содержит несколько указанных значений параметров)
- Пример:
var obj = {
value: 1,
}
function foo (name, old) {
return {
value:this.value,
name,
old
}
}
foo.apply(obj, ['tom', 12], 24); // {value: 1, name: "tom", old: 12}
- Требовать:
- вызов меняет суть этого,
- функция foo выполняется
- Поддерживает передачу параметров, второй параметр является массивом, следующие параметры недействительны, а количество параметров в массиве не фиксировано.
- Этот параметр может быть передан null, при передаче null он указывает на окно
- Функции могут иметь возвращаемые значения
- Нефункциональная обработка оценки вызова
- реализация и идеи
Function.prototype.apply1 = function (context, args) { // 与call实现的唯一区别,此处不用解构
if(typeof this !== 'function') {throw new Error('Error')} // 非函数调用判断处理
context = context || window; // 非运算符判定传入参数 null则指向window
const key = Symbol(); // 利用symbol创建唯一的属性名,防止覆盖原有属性
context[key] = this; // 把函数作为对象的属性,函数的this就指向了对象
const result = context[key](...args) // 临时变量赋值为执行对象方法
delete context[key]; // 删除方法,防止污染对象
return result // return 临时变量
}
- Сценарии применения
- то же, что звонить
5. bind
- определение
- Метод bind создает новую функцию,
- Когда вызывается новая функция, первым параметром является this во время выполнения,
- Следующие параметры будут переданы в качестве его параметров перед передачей фактических параметров.
- Пример
const obj = {
value: 1
};
function foo(name, old) {
return {
value: this.value,
name,
old
}
}
const bindFoo = foo.bind(obj, 'tom');
bindFoo(12); // {value: 1, name: "tom", old: 12}
- Требовать
- Возвращает функцию с this в качестве первого параметра при выполнении
- Вы можете передать часть параметров при привязке, а затем передать другую часть параметров при выполнении функции
- Функция привязки используется как конструктор, это недопустимо, но параметры допустимы,
- И в качестве конструктора прототип должен указывать на прототип связанной функции, чтобы экземпляр мог наследовать значение в прототипе.
- Обработка суждения о привязке нефункционального вызова
- реализация и идеи
Function.prototype.bind1 = function (context, ...args) {
if(typeof this !== 'function') {throw new Error('Error')} // 非函数调用判断处理
const self = this; // 保存当前执行环境的this
const Foo = function() {} // 保存原函数原型
const res = function (...args2) { // 创建一个函数并返回
return self.call( // 函数内返回
this instanceof res ? this : context, // 作为构造函数时,this指向实例,:作为普通函数this正常指向传入的context
...args, ...args2) // 两次传入的参数都作为参数返回
}
Foo.prototype = this.prototype; // 利用空函数中转,保证在新函数原型改变时bind函数原型不被污染
res.prorotype = new Foo();
return res;
}
6. new
посмотриnew
Что может пример
- Доступные свойства конструктора
- Доступ к свойствам прототипа
- Если конструктор имеет возвращаемое значение, он возвращает объект, а экземпляр может получить доступ только к свойствам возвращаемого объекта, это недопустимо.
- Возвращает значение базового типа, нормальная обработка, это допустимо
function Persion(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log(this.name)
}
var per = new Person('tom', 10)
per.name // 10 可访问构造函数的属性
per.sayName // tom 访问prototype的属性
новая реализация
var person = factory(Foo)
function factory() { // new是关键字,无法覆盖,函数替代
var obj = {}; // 新建对象obj
var con = [].shift.call(arguments); // 取出构造函数
obj._proto_ = con.prototype; // obj原型指向构造函数原型
var res = con.apply(obj, arguments); // 构造函数this指向obj
return typeof(res) === 'object' ? ret : obj;
}
7. Наследование
- Цепное наследование прототипов и прототипное наследование
- Невозможно передать параметры родительскому классу, только совместно использовать свойства
- Заимствование конструкторов и паразитическое наследование
- Метод не находится на прототипе и должен каждый раз создаваться заново.
- наследование композиции
- Хотя две вышеупомянутые проблемы решены, но отец вызывается дважды,
- Одни и те же свойства будут использоваться в экземпляре и прототипе.
- Наследование паразитарного состава
- Текущее лучшее решение
Реализация наследования паразитной композиции
function P (name) { this.name = name; } // 父类上绑定属性动态传参
P.prototype.sayName = function() { // 父类原型上绑定方法
console.log(111)
}
function F(name) { // 子类函数里,父类函数利用`call`函数this指向子类,传参并执行
P.call(this, name)
}
const p = Object.create(P.prototype) // Object.create 不会继承构造函数多余的属性和方法
p.constructor = F; // constructor属性丢失,重新指向
F.prototype = p; // 子类原型 指向 中转对象
const c = new F('tom'); // 子类实例 化
c.name // tom
c.sayName() // 111
3. JS-данные
1. Тип данных
JS делится на базовые типы и ссылочные типы.
- основной тип:
Boolean
Undefined
String
Number
Null
Symbol
- Тип ссылки:
Object
Funciton
Array
Ждать
2. Хранение данных (глубокое и поверхностное копирование)
js типы данных делятся на базовые типы и ссылочные типы.
- Примитивные типы хранятся в памяти стека
- При присвоении значения система компиляции заново создает часть памяти для хранения новой переменной, поэтому связь обрывается после присвоения переменной базового типа.
- Ссылочные типы хранятся в куче памяти
- При назначении это только копия адреса объекта, новая память не открывается, и копия адреса кучи памяти, обе указывают на один и тот же адрес
- Измените один из них, и другой будет затронут
Два объекта указывают на один и тот же адрес, изменение одного повлияет на другой
Специальные методы объекта массива (глубокое копирование одного уровня)
-
obj2 = Object.assign({}, obj1)
-
arr2 = [].concat(arr1)
-
arr2 = arr1.slice(0)
-
arr2 = Array.form(arr1)
-
arr2 = [...arr1];
Вышеупомянутые методы могут глубоко копировать только один слой
JSON.parse(JSON.stringify(obj)) (многослойный)Вместо копирования объекта копирование строки откроет новый адрес памяти и прервет связь указателя со ссылочным объектом.
недостаток
- объект времени => строковая форма
- RegExp, Error => просто получить пустой объект
- функция,неопределенная => потеряна
- NaN, Infinity и -Infinity => сериализовать в ноль
- Объект генерируется конструктором => конструктор, который отбрасывает объект
- Глубокое копирование невозможно при наличии циклических ссылок
Вручную внедрить глубокое копирование (многослойное)
function Judgetype(e) {
return Object.prototype.toString.call(e).slice(8, -1).toLowerCase();
}
function Loop(param) {
let target = null;
if(Judgetype(param) === 'array') {
target = [];
for(let key of param.keys()){
target[key] = Deep(param[key]);
}
} else {
target = {};
Object.keys(obj).forEach((val) => {
target[key] = Deep(param[key]);
})
}
return target;
}
function Deep(param) {
//基本数据类型
if(param === null || (typeof param !== 'object' && typeof param !== 'function')) {
return param;
}
//函数
if(typeof param === 'function') {
return new Function('return ' + param.toString())();
}
return Loop(param);
}
3. Оценка типа данных (оценка типа, равенство и соответствие, неявное преобразование, равенство двух объектов)
- Типовое суждение
typeof
无法区分 object, null 和 array
- Возвращает правильные результаты для примитивных типов, кроме null.
- Для ссылочных типов, кроме функции, все возвращаемые типы объектов
typeof(1) // number
typeof('tom') // string
typeof(undefined) // undefined
typeof(null) // object
typeof(true) // boolean
typeof(Symbol(1)) // symbol
typeof({a: 1}) // object
typeof(function () {}) // function
typeof([]) // object
instanceof
- Определить, относится ли экземпляр к определенному типу
[] instanceof Array; // true
{} instanceof Object;// true
var a = function (){}
a instanceof Function // true
Object.prototype.toString.call
-
Текущее лучшее решение
-
toString() — это метод-прототип Object, вызов которого по умолчанию возвращает [[Class]] текущего объекта. Это внутреннее свойство в формате [object Xxx] , где Xxx — тип объекта.
-
Для объектов Object вызов toString() напрямую возвращает [object Object]. Для других объектов его необходимо вызывать через call/apply, чтобы вернуть правильную информацию о типе.
-
这里就是call的应用场景,巧妙利用call借用Object的方法实现类型的判断
function T(e) {
return Object.prototype.toString.call(e).slice(8, -1).toLowerCase();
}
T(1) // number
T('a') // string
T(null) // null
T(undefined) // undefined
T(true) // boolean
T(Symbol(1)) // symbol
T({a: 1}) // object
T(function() {}) // function
T([]) // array
T(new Date()) // date
T(/at/) // RegExp
- равные и конгруэнтные
-
==
Нестрогое сравнение допускает преобразование типов -
===
Строгое сравнение не допускает преобразования типов
Адрес хранится в стеке ссылочного типа данных, а содержимое хранится в куче.Даже если содержимое одинаковое, адрес отличается, поэтому они не равны.
const a = {c: 1}
const b = {c: 1}
a === b // false
- неявное преобразование
-
==
Могут быть преобразования типов не только при сравнении на равенство, но и при выполнении операций, которые мы называем неявными преобразованиями.
Логическое сравнение, сначала поворачивайте числа
true == 2 // false
||
1 == 2
// if(X)
var X = 10
if(X) // true
10 ==> true
// if(X == true)
if(X == true) // false
10 == true
||
10 == 1
Сравнивать числа со строками,字符串
Перемена数字
0 == '' // true
||
0 == 0
1 == '1' // true
||
1 == 1
Сравнение равенства между объектными типами и примитивными типами
[2] == 2 // true
|| valueOf() // 调用valueOf() 取自身值
[2] == 2
|| toString() // 调用toString() 转字符串
"2" == 2
|| Number() // // 数字和字符串做比较,`字符串`转`数字`
2 == 2
резюме
js использует определенные операторы для преобразования типов, обычно +, ==
- во время операции
- Дополнение существует строка и преобразует ее в строку
- Умножить - Разделить - Вычесть Строки преобразуются в числа
- При сравнении на равенство
- Логическое сравнение, сначала поворачивайте числа
- Сравнивать числа со строками,
字符串
Перемена数字
- Сравнение типов объектов для преобразования исходного типа
Внеклассные вопросы Осознайте a == 1 && a == 2 && a == 3
const a = {
i: 1,
toString: function () {
return a.i++;
}
}
a == 1 && a == 2 && a == 3
- Определить, равны ли два объекта
- Адрес хранится в стеке ссылочного типа данных, а содержимое хранится в куче, даже если содержимое такое же, а адрес другой, поэтому
===
Эти двое все еще не равны при оценке - Но как мы можем судить об их равенстве, когда содержание справочных данных одинаково?
// 判断两者非对象返回
// 判断长度是否一致
// 判断key值是否相同
// 判断相应的key值里的对应的值是否相同
这里仅仅考虑 对象的值为object,array,number,undefined,null,string,boolean
关于一些特殊类型 `function date RegExp` 暂不考虑
function Judgetype(e) {
return Object.prototype.toString.call(e).slice(8, -1).toLowerCase();
}
function Diff(s1, s2) {
const j1 = Judgetype(s1);
const j2 = Judgetype(s2);
if(j1 !== j2){
return false;
}
if(j1 === 'object') {
if(Object.keys(s1).length !== Object.keys(s2).length){
return false;
}
s1[Symbol.iterator] = function* (){
let keys = Object.keys( this )
for(let i = 0, l = keys.length; i < l; i++){
yield {
key: keys[i],
value: this[keys[i]]
};
}
}
for(let {key, value} of s1){
if(!Diff(s1[key], s2[key])) {
return false
}
}
return true
} else if(j1 === 'array') {
if(s1.length !== s2.length) {
return false
}
for(let key of s1.keys()){
if(!Diff(s1[key], s2[key])) {
return false
}
}
return true
} else return s1 === s2
}
Diff( {a: 1, b: 2}, {a: 1, b: 3}) // false
Diff( {a: 1, b: [1,2]}, {a: 1, b: [1,3]}) // false
На самом деле, возврат обхода объекта также может быть использован для того, чтобы побочные эффекты обхода прототипов можно было компенсировать с помощью суждения hasOwnproperty.
- Объект для обхода должен самостоятельно добавлять итератор, что более хлопотно
for(var key in s1) {
if(!s1.hasOwnProperty(key)) {
if(!Diff(s1[key], s2[key])) {
return false
}
}
}
4. Манипуляции с данными (обход массива, обход объекта)
1. Обход массива
`最普通 for循环` // 较为麻烦
for(let i = 0,len = arr.length; i < len; i++) {
console.log(i, arr[i]);
}
`forEach` 无法 break return
`for in` 不适合遍历数组,
`for...in` 语句在w3c定义用于遍历数组或者对象的属性
1. index索引为字符串型数字,不能直接进行几何运算
2. 遍历顺序有可能不是按照实际数组的内部顺序
3. 使用for in会遍历数组所有的可枚举属性,包括原型
`for of` 无法获取下标
// for of 兼容1
for(let [index,elem] of new Map( arr.map( ( item, i ) => [ i, item ] ) )){
console.log(index);
console.log(elem);
}
// for of 兼容2
let arr = [1,2,3,4,5];
for(let key of arr.keys()){
console.log(key, arr[key]);
}
for(let val of arr.values()){
console.log(val);
}
for(let [key, val] of arr.entries()){
console.log(key, val);
}
2.
2. Обход объекта
- for in
Недостаток: он будет проходить через все перечисляемые свойства объекта, например, в прототипе.
var obj = {a:1, b: 2}
obj.__proto__.c = 3;
Object.prototype.d = 4
for(let val in obj) {
console.log(val) // a,b,c,d
}
// 优化
for(let val in obj) {
if(obj.hasOwnProperty(val)) { // 判断属性是存在于当前对象实例本身,而非原型上
console.log(val) // a,b
}
}
- object.keys
var obj = { a:1, b: 2 }
Object.keys(obj).forEach((val) => {
console.log(val, obj[val]);
// a 1
// b 2
})
- for of
- for-of можно использовать только для типов данных, которые предоставляют интерфейс Iterator.
- Такие типы, как Array, предоставляются по умолчанию.
- Мы можем добавить к объекту свойство Symbol.iterator
var obj = { a:1, b: 2 }
obj[Symbol.iterator] = function* (){
let keys = Object.keys( this )
for(let i = 0, l = keys.length; i < l; i++){
yield {
key: keys[i],
value: this[keys[i]]
};
}
}
for(let {key, value} of obj){
console.log( key, value );
// a 1
// b 2
}
5. Расчет данных (ошибка расчета)
0.1 + 0.2 = 0.30000000000000004
- Все числа будут преобразованы в двоичные и рассчитаны побитно,
- Десятичный, двоичный нельзя разделить на два, он будет бесконечно зацикливаться
- js данные хранят 64-битные числа двойной точности с плавающей запятой, которые здесь повторяться не будут, лишнее будет перехвачено (из-за этого тоже большие ошибки вычисления числа и ограничения)
- После добавления и обратного конвертирования будут ошибки
- Так как же сделать точный расчет?
- Для десятичных дробей, у которых сами десятичные дроби не превышают количество цифр, хранящихся в js, они могут быть одновременно преобразованы в целые числа, а затем после вычисления преобразованы в десятичные дроби.
function getLen(n) {
const str = n + '';
const s1 = str.indexOf('.')
if(s1) {
return str.length - s1 - 1
} else {
return 0
}
}
function add(n1, n2) {
const s1 = getLen(n1)
const s2 = getLen(n2)
const max = Math.max(s1, s2)
return (n1 * Math.pow(10, max) + n2 * Math.pow(10, max)) / Math.pow(10, max)
}
add(11.2, 2.11) // 13.31
- Для тех, которые превышают количество цифр хранения, преобразуйте его в массив, добавьте его побитно в обратном порядке, а если оно больше 10, склеивайте строки, чтобы получить значение
function add(a, b) {
let i = a.length - 1;
let j = b.length - 1;
let carry = 0;
let ret = '';
while(i>=0|| j>=0) {
let x = 0;
let y = 0;
let sum;
if(i >= 0) {
x = a[i] - '0';
i--
}
if(j >=0) {
y = b[j] - '0';
j--;
}
sum = x + y + carry;
if(sum >= 10) {
carry = 1;
sum -= 10;
} else {
carry = 0
}
ret = sum + ret;
}
if(carry) {
ret = carry + ret;
}
return ret;
}
add('999999999999999999999999999999999999999999999999999999999999999', '1')
// 1000000000000000000000000000000000000000000000000000000000000000
4. JS-приложение
1. Защита от сотрясения
Сцены:
- Введите поле поиска и выпадающее меню Lenovo, чтобы запросить фоновый интерфейс.Во избежание частых запросов это будет оказывать давление на сервер
определение:
- Выполненные n секунд после того, как событие срабатывает, и событие срабатывает в течение n секунд события, в зависимости от того, что
Реализовать идею:
- Выполнение и сброс таймеров
- применить изменения к этому пункту
- применить наследование параметров
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
2. Дросселирование
Сцены:
- Некоторые события могут запускаться реже.
- Например, при ленивой загрузке нужно следить за положением полосы прокрутки вычислений, но ее не нужно срабатывать каждый раз при скольжении, что позволяет снизить частоту вычислений без траты ресурсов;
определение:
- Непрерывно запускайте событие и выполняйте его только один раз в течение указанного времени.
- Способ 1: Отметка времени
- Реализовать идею:
- Время триггера теперь принимает текущую отметку времени минус отметку времени флага (начальное значение равно 0).
- Если оно больше указанного времени, выполнить, и флаг обновится до текущего времени,
- Если оно меньше указанного времени, оно не будет выполнено
function foo(func, wait) {
var context, args;
var flag = 0;
return function () {
var now = +new Date();
context = this;
args = arguments;
if(now - flag > 0) {
func.apply(context, args);
flag = now;
}
}
}
- Способ 2: Таймер
- Реализовать идею:
- Определить, есть ли таймер в данный момент,
- Если нет, определите таймер, запустите его в указанное время и очистите таймер.
- не выполнять
function foo(func, wait) {
var context, args;
var timeout;
return function() {
if(!timeout){
setTimeout(()=>{
timeout = null;
func.apply(context, args);
}, wait)
}
}
}
3. Карри
- Определение: Преобразует функцию, которая принимает несколько параметров, в функцию, которая принимает один параметр, и возвращает новую функцию, которая принимает оставшиеся параметры и возвращает результат.
- Функции:
参数复用
,提前返回
,延迟执行
- Процесс реализации
- Создайте функцию, используйте команду «Применить» и повторно передайте объединенные параметры каррированной функции.
- Используйте сокращение для перебора всех элементов в массиве и создания окончательного возвращаемого значения.
function add(...args) {
var fn = function(...args1) {
return add.apply(this, [...args, ...args1]);
}
fn.toString = function() {
return args.reduce(function(a, b) {
return a + b;
})
}
return fn;
}
add(1)(2)(3).toString(); // 6