Для глубины фронтенда — понятие и применение замыканий

внешний интерфейс

Суммировать

Определение: замыкания позволяют функции получать доступ к переменным и функциям и манипулировать ими в той области видимости, в которой она была объявлена, и могут быть вызваны, даже если область действия во время объявления исчезает.

применение:

  1. частная переменная
  2. Обратные вызовы и таймеры
  3. привязать контекст функции
  4. Частично примененная функция
  5. Перегрузка функций: кэш-память, перенос функций
  6. Непосредственные функции: независимая область видимости, лаконичный код, циклы, обертки библиотек классов, ограничение имен в области видимости параметрами

предисловие

В последнее время я был занят проектами компании, и у меня нет времени продолжать интервью и оскорбления, я нашел время только для чтения «JavaScript Ninja Cheats».

Я был немного взволнован и бессонница сегодня вечером, поэтому я просто написал краткое изложение знаний закрытия.

Содержание в основном все из читов ниндзя.Если вы думаете, что это хорошо написано, вы можете прочитать книгу внимательно; если вы думаете, что это плохо написано, это может быть потому, что я не понимаю его должным образом, что приводит к ошибкам в моем собственном мышлении в тексте, или я могу опустить это.Пошаговый процесс в книге приводит к пропуску некоторых знаний. По разным причинам, пожалуйста, поправьте меня.

текст

Прочитав много статей, они говорят об определении замыканий и преимуществах и недостатках замыканий. Как по мне, добавим применение замыканий.

Определение замыкания можно найти во многих статьях, я помню, что есть угол, который говорит, что пока есть доступ к внешним переменным, это замыкание, и есть другой угол, что все функции являются замыканиями.

Я думаю, что эти ответы правильные, но интервьюеру неудобно продолжать спрашивать или ему нелегко направлять интервьюера. Итак, если бы я брал интервью, я бы использовал определение из The Ninja Cheats:Замыкание — это область, созданная при создании функции, позволяющая собственной функции получать доступ и манипулировать переменными вне собственной функции.. Это немного запутанно, более ясная версия:Замыкания позволяют функции получать доступ и манипулировать переменными и функциями в объявленной области, и даже если объявленная область исчезает, могут быть вызваны. Следует отметить, что замыкание — это не снимок состояния на момент создания, а реальная инкапсуляция, которую можно модифицировать, пока существует замыкание.

Самое простое закрытие:

// 全局作用于就是一个闭包
var outerVal = 'lionel';
function outerFn(){
  console.log(outerVal)
}
outerFn() // lionel

Сложнее то, что мы думаем:

var outerVal = 'lionel';
var later;
function outerFn(){
  var innerVal = 'karma';
  function innerFn(){
    console.log(outerVal, innerVal);
  }
  later = innerFn;
}
outerFn();  // 此时outerFn的作用域已经消失了
later();  // lionel karma

Непонятно, этот пример мы можем понять, замыкания не являются снимками:

var later;
function outerFn(){
  function innerFn(){
    console.log(lateVal)
  }
  later = innerFn;
}
console.log(lateVal); // undefined
var lateVal = 'lionel'; // 变量提升,闭包声明的那一刻存在这个变量
outerFn();
later(); // lionel

Недостаток всем знаком, информация в замыкании всегда будет храниться в памяти. Решение состоит в том, чтобы очищать ссылки там, где, по вашему мнению, можно, как в приведенном выше примере, использовать Later = null , чтобы замыкание могло быть очищено при следующей сборке мусора.

Сосредоточимся на закрытиипрактическое применение.

1. Частные переменные

Распространенное использование замыканий, инкапсулирующих приватные переменные. Пользователь не может напрямую получить и изменить значение переменной, но должен вызвать метод, и такое использование может создать частную переменную, доступную только для чтения. Мы понимаем из следующего примера:

function People(num) { // 构造器
  var age = num;
  this.getAge = function() {
    return age;
  };
  this.addAge = function() {
    age++;
  };
}
var lionel = new People(23); // new方法会固化this为lionel哦
lionel.addAge();
console.log(lionel.age);      // undefined
console.log(lionel.getAge()); // 24
var karma = new People(20);
console.log(karma.getAge()); // 20

Как показано на рисунке ниже, атрибут age не существует в lionel, age существует только в области видимости нового процесса, а в getAge и addAge мы видим, что их область действия содержит замыкание People.

alt

2. Обратные вызовы и таймеры

Я мало говорил об этой части.

3. Контекст функции привязки

Я могу быть немного сбит с толку, когда впервые увидел это приложение. Если хорошенько подумать, мы видели его много раз. Вот как реализована функция bind(). Вот еще одна простая реализация кода:

Function.prototype.myBind = function() {
  var fn = this,
      args = [...arguments],
      object = args.shift();
  return function() {
    return fn.apply(object, args.concat(...arguments))
  }
}

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

В-четвертых, функция частичного приложения

Функция частичного применения возвращает функцию с параметрами предварительной обработки, чтобы ее можно было вызвать позже. Просто посмотрите на код

Function.prototype.partial = function() {
  var fn = this,
      args = [...arguments];
  return function() {
    var arg = 0;
    var argsTmp = [...args]
    for (var i=0; i<argsTmp.length && arg < arguments.length; i++) {
      if (argsTmp[i] === undefined) {
        argsTmp[i] = arguments[arg++]
      }
    }
    return fn.apply(this, argsTmp)
  }
}
function addAB(a ,b) {
  console.log( a + b);
}
var hello = addAB.partial('hello ', undefined);
hello('lionel'); // hello lionel
hello('karma'); // hello karma
var bye = addAB.partial(undefined, ' bye')
bye('lionel'); // lionel bye
bye('karma'); // karma bye

Приведенный выше пример может быть немного сложным для понимания, вот упрощенный пример:

function add(a) {
  return function(b) {
    console.log( a + b);
  };
}
var hello = add('hello ')
hello('lionel'); // hello lionel
hello('karma'); // hello karma

эммм... Я долго писал сюда, чтобы изучить разницу между каррированием и частичными функциями, и наконец нашел статью, которая соответствует моей идее: частичные функции и каррирование функций, пожалуйста, поправьте меня, если я ошибаюсь.

Пять, перегрузка функций

1 кэш-память

Мы можем обернуть функцию через замыкание, чтобы человек, который вызывает нашу функцию, не знал, что мы приняли метод кэширования, или, другими словами, результат вычисления может быть кэширован без каких-либо дополнительных действий со стороны вызывающей стороны.

Function.prototype.memoized = function(key) {
  this._values = this._values || {};
  return this._values[key] !== undefined ?
    this._values[key] + ' memoized' :
    this._values[key] = this.apply(this, arguments);
}
Function.prototype.memoize = function() {
  var fn = this;
  return function() {
    // return fn.memoized.apply(fn, arguments);
    console.log(fn.memoized.apply(fn, arguments))
  }
}
var computed = (function(num){
  // 这里有超级超级复杂的计算,耗时特别久
  console.log('----计算了很久-----')
  return 2
}).memoize();
computed(1); // ----计算了很久-----     2
computed(1); // 2 memoized

2 функциональная оболочка

Пример ниже написан не так хорошо, как в книге.

function wrap(object, method, wrapper){
  var fn = object[method];
  return object[method] = function() {
    return wrapper.apply(this, [fn.bind(this)].concat(...arguments))
  }
}
let util = {
  reciprocal: function(tag){
    console.log(1 / tag)
  }
}

wrap(util, 'reciprocal', function(original, tag){
   return tag == 0 ? 0 : original(tag)
})

util.reciprocal(0);  // 0

6. Немедленная функция

Следующий рисунок является хорошей иллюстрацией того, почему мгновенные функции вводятся в замыканиях:

alt

1 независимый прицел

var button = $('#mybtn');
(function(){
  var numClicks = 0;
  button.click = function(){
    alert(++numClicks)
  }
})

2 Краткий код

// 例如有如下data
data = {
  a: {
    b: {
      c: {
        get: function(){},
        set: function(){},
        add: function(){}
      }
    }
  }
}
// 第一种调用这三个方法的代码如下, 繁琐
data.a.b.c.get();
data.a.b.c.set();
data.a.b.c.add();
// 第二种方法如下, 引入多余变量
var short = data.a.b.c;
short.get();
short.set();
short.add();
// 第三种使用即时函数 优雅
(function(short){
  short.get();
  short.set();
  short.add();
})(data.a.b.c)

3 цикла

Эта часть вызывает setTimeout в классическом цикле for для печати i. Причина, по которой i выводится как фиксированное значение, заключается в том, что замыкание — это не моментальный снимок, а ссылка на переменную. Когда оно выполняется в асинхронной очереди, i изменилось.

Решение состоит в том, чтобы использовать другое замыкание и непосредственную функцию.

4 оболочки библиотеки классов

// 下方的代码展示了,为什么jquery库中,它可以放心的用jquery而不担心这个变量被替换
(function(){
  var jQuery = window.jQuery = function() {
    // Initialize
  };
  // ...
})()

5 Ограничение имен в области действия параметрами

// 当我们担心jquery中的$符号,被其他库占用,导致我们代码出问题的时候,
// 用下面的方法,就可以放心大胆的用啦(不过要注意:如果jQuery也被占用的话就...)
(function($){
  $.post(...)
})(jQuery)