Статьи о функциях JS (4,3 Вт слов)

JavaScript
Статьи о функциях JS (4,3 Вт слов)

Эта серия завершится следующими темами:

1. Подробное изложение базовых знаний JS
2. Расширенный объект
3. Расширенная функция
4. Объект события и механизм события

А пока я подведу итоги по четырем вышеприведенным темам, а теперь начну Часть 3: Расширенные функции. На картинке ниже схема моей статьи.

js函数学习大纲

3.1 Краткое описание использования этого

this— это внутренний указатель на объект, который автоматически создается во время выполнения функции. если быть точным,thisНе объект, а указатель на существующий объект, который также можно рассматривать какthisОн хранит адрес объекта.

thisУказатель не является фиксированным, он будет указывать на разные места в соответствии с разными вызовами. Идея этой главы заключается в следующем:

this总结

3.1.1 Понять предварительное знание, указанное в этом

  • Дальнейшее понимание переменных и функций, определенных в глобальной области видимости

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

    Поняв приведенное выше предложение, давайте объясним разницу, на которую многие не обратили внимания.

    console.log(window.a);  //undefined
    console.log(a);   //报错!a is not defined 
    

    Примечание. То есть, когда переменная (a), чтобы объявить вышеуказанные результаты. Прежде всего, ясно, что глобальные переменныеaдаwindowхарактеристики. Итак, мы можем узнать отсюда, когдаundefined, Когда дается? - то есть, если свойство является доступом к объекту, оно не объявляет присваивание, котороеundefined; Если вы обращаетесь к переменной, для которой не объявлено присвоение, это ошибка.

    Хорошо, а теперь вернемся назад, давайте посмотрим на знание переменных и функций глобальной области видимости.

    <script type="text/javascript">
          var num = 10;      //全局作用域声明的变量
          function sum () {  //全局作用域声明的函数
              alert("我是一个函数");
          }
          alert(window.num);  // 10
          window.sum();       // 我是一个函数
          // 在调用的时候window对象是可以省略的。
       </script>
    
  • Разъяснение конструкторов и неконструкторов

    В JavaScript нет существенной разницы между конструкторами и неконструкторами.Разница только в том, как это называется.

    • Использование нового конструктора
    • Прямой вызов не является конструктором

    См. пример кода:

    <script type="text/javascript">
          function Person () {
              this.age = 20;
              this.sex = "男";
          }
          //作为构造函数调用,创建一个对象。 这个时候其实是给p添加了两个属性
          var p = new Person();
          alert(p.age + "  " + p.sex);
    
          //作为普通函数传递,其实是给 window对象添加了两个属性 
          //任何函数本质上都是通过某个对象来调用的,如果没有直接指定就是window,也就是Window可省略
          Person();
          alert(window.age + " " + window.sex);
      </script>
    

3.1.2 Первое направление: это в глобальном масштабе указывает на

используется в глобальном масштабеthis, который нельзя использовать внутри какой-либо функцииthis, то на этот разthisозначаетWindow

<script type="text/javascript">
	//全局作用域中的this
    //向this对象指代的对象中添加一个属性 num, 并让属性的值为100
    this.num = 100;
    // 因为this就是window,所以这时是在修改属性num的值为200
    window.num = 200;
    alert(this === window);  // true this就是指向的window对象,所以是恒等 
    alert(this.num);    //200   
    alert(window.num);  //200
</script>

3.1.3 Второе направление: this в функции

в функцииthisЕго можно разделить на конструктор и неконструктор.thisдва понятия для понимания.

  • в неконструктореthisнаправление

    в неконструктореthisуказывает наЭтот объект для вызова этого метода

Пример 1:

<script type="text/javascript">
      function test() {
          alert(this == window);
          this.age = 20;
      }
      test();  //其实是 window.test();  所以这个时候test中的this指向window
  </script>

Пример 2:

 <script type="text/javascript">
      var p = {
          age : 20,
          sex : "男",
          sayAge: function (argument) {           
              alert(this.age);
          }
      }
      p.sayAge(); //调用对象p的方法sayAge()  所以这个时候this指的是 p 这个对象
  </script>

Пример 3:

  <script type="text/javascript">
      var p = {
          age : 20,
          sex : "男",
          sayAge: function (argument) {
              alert(this.age);
              alert(this === p);  //true
          }
      }
      var again = p.sayAge;   //声明一个变量(方法),把p的方法复制给新的变量
//调用新的方法: 其实是window.again().  
//所以 方法中的this指代的是window对象,这个时候age属性是undefined
// this和p也是不相等的。 
      again();    
  </script>

В итоге:thisСсылка не имеет ничего общего с тем, где появляется код, а только с объектом, для которого вызывается метод.

  • this в конструкторе указывает на

в конструктореthisОтносится к объекту, который будет создан в будущем.

Пример 1:

<script type="text/javascript"> 
      function Person () {
          this.age = 20;
          return this;  //作为构造函数的时候,这个行代码默认会添加
      }
      var p1 = new Person();  //这个时候 Person中的this就是指的p1
      var p2 = new Person();  //这是时候 Person中的this就是知道p2
  </script>

Узнать больше: это практичноnewКогда вызывается конструктор, внутри конструктора фактически находится значение по умолчанию.return this; поэтомуthisОтносится к создаваемому объекту.

3.1.4 Третье направление: изменить точку this (явная привязка)

В JavaScript разрешить изменениеthisуказывает на. пройти черезcallметод илиapplyметод

Функцию А можно вызвать как метод, задающий любой объект. Функция A является функциональным объектом, и каждый функциональный объект имеет методcall,пройти черезcallВы можете позволить указанному вами объекту вызывать эту функцию A.

Спецификация ECMAScript определяет все функцииcallа такжеapplyдва метода.callа такжеapplyОн помещается в объект-прототип функции, а не в объект-прототип объекта!

<script type="text/javascript"> 
    var age = 20;
    function showPropertyValue (propertyName) {
        alert(this[propertyName]);
    }
    //使用call的时候,第一个参数表示showPropertyValue中的this的执行,后面的参数为向这个函数传的值。
    //注意一点:如果第一个参数是null,则this仍然是默认的指向。
    showPropertyValue.call(null, "age");
    showPropertyValue.call(this, "age");
    showPropertyValue.call({age:50}, "age")
</script>

3.1.5 Подробное объяснение call/apply/bind

существуетthisСуществует третье направление в направленииcall/applyизменитьthisУказатель на, это уникальная форма использования в JavaScript, которой нет в других языках. Итак, давайте поговорим об этом здесь, кстатиcall,applyтак же какbindПрименение.

В этом разделе будут рассмотрены три аспекта: 1:applyа такжеcallразница 2:applyа такжеcallПрименение 3:callа такжеbindразница

3.1.5.1 Разница между применением и вызовом

Спецификация ECMAScript определяет все функцииcallа такжеapplyЭти два метода широко используются, и их функции совершенно одинаковы, но форма передачи параметров различна.

Проще говоря, предположим, что есть функция A, мы вызываем функцию A и переходим непосредственно кA(), то еслиA()Если это вызывается напрямую, тело функции Athisто естьwindow. и мы можем пройтиcall(илиapply) для вызова, например:A.call()Таким образом, вы можете указать, каким объектом является this в A.

использоватьcallДля сравнения в нем есть два параметра, первый параметр - повторно указать один из нихthisкто есть, параметр 2 - это имя атрибута. И на самом деле,callа такжеapplyТо есть разница второго параметра.

apply applyМетод передает два параметра: один — объект в качестве контекста функции, короче говоря, переопределяетthisкто это. Другой представляет собой массив, сформированный как параметр функции, который передается в массиве.

var obj = {
    name : 'ya LV'
}

function func(firstName, lastName){
    console.log(firstName + ' ' + this.name + ' ' + lastName);
}

func.apply(obj, ['A', 'B']);    // A ya LV B

можно увидеть,objобъект, являющийся контекстом функции, то есть функцияfuncсерединаthisуказал наobjэтот объект. Первоначально, если звонить напрямуюfunc(), то в теле функцииthisозначаетwindow. Но теперь есть параметр один, который заключается в повторном указанииthis,этоthisпараметр одинobjэтот объект. Параметры A и B передаются в массивеfuncфункция, соответствующаяfuncЭлемент списка параметров.

call callПервый параметр метода также является объектом, который является контекстом функции. а такжеapplyНет никакой разницы. Но передается список параметров, а не один массив.

var obj = {
    name: 'ya LV'
}

function func(firstName, lastName) {
    console.log(firstName + ' ' + this.name + ' ' + lastName);
}

func.call(obj, 'C', 'D');       // C ya LV D

В сравненииapplyМы видим разницу, C и D передаются как отдельные параметры вfuncфункцию вместо помещения ее в массив.

Не беспокойтесь о том, когда какой метод использовать. Если ваши параметры уже существуют в массиве, то, естественно, используйтеapply, если параметры разбросаны и не связаны друг с другом, используйтеcall.

добавить использованиеapplyпример. Например, чтобы найти максимальное значение массива?

Ясно, что в JavaScript нет функции, которая возвращает максимальное значение в массиве. Однако есть функцияMath.maxможет возвращать любое количество числовых типов

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

В настоящее время, если вы хотите использовать функцию Math.max, передаваемый параметр может быть массивом. Мы, естественно, думаем, что все функции определеныcallа такжеapplyметод, мы можем поместитьсяapplyилиcallдобиться, а потомуcallПереданный параметр не является массивом. Все, что мы выбираемMath.maxфункция плюсapplyможет достичь нашей цели.

Первоначально использовался только так и не может напрямую использовать массив, см. Пример:

let max = Math.max(1, 4, 8, 9, 0)

имеютapply, вы можете назвать это так:

let arr = [1, 4, 8, 9, 0];
let max = Math.max.apply(null, arr);

вызовapplyКогда первый параметр даетnull, это потому, что нет объекта для вызова этого метода, нам нужно использовать этот метод только для вычисления и получения возвращаемого результата, поэтому мы напрямую передаемnullмимо.

3.5.1.2 Использование применения и вызова

applyа такжеcallИспользование можно разделить на три части: изменениеthisУказатель на заимствование метода другого объекта, вызовите функцию.

  • 1. Измените это, чтобы указать на
var obj = {
      name: 'ya LV'
  }

  function func() {
      console.log(this.name);
  }

  func.call(obj);       // ya LV

Это было рассмотрено в предыдущем разделе, поэтому мы просто рассмотрим его еще раз. Так называемая "практика делает совершенным", одно дело, точка знания, каждый раз, когда вы видите это, у вас будет разный опыт. Может быть, процесс просмотра на этот раз заставляет вас думать более глубоко, что является прогрессом.callПервым параметром метода является объект как контекст функции, здесьobjпередается в качестве параметраfunc, то в функцииthisуказал наobjобъект. здесьfuncФункция фактически эквивалентна:

 function func() {
      console.log(obj.name);
  }

Также обратите вниманиеcallНекоторые специальные применения , очень странныеthisнаправление. Немного внимания, немного впечатления — это хорошо.

  function func() {
      console.log(this);
    }
    func.call();   //window
    func.call(undefined);     //window
    func.call(null);    //window
    func.call(1);  //Number {1}   这种情况会自动转换为包装类Number 就相当于下面一行代码
    func.call(new Number(1));   //Number {1}
  • 2. заимствовать методы другого объекта
 var Person1  = function () {
      this.name = 'ya LV';
  }

  var Person2 = function () {
      this.getname = function () {
          console.log(this.name);
      }
      Person1.call(this);
  }
  var person = new Person2();
  person.getname();       // ya LV

Мы видим сверху,Person2созданный объектpersonпройти черезgetnameметод получилPerson1серединаname. Потому чтоPerson2середина,Person1.call(this)это использоватьPerson1объект вместо этогоthisобъект, тоPerson2естьPerson1Все свойства и методы в , эквивалентныеPerson2наследоватьPerson1свойства и методы. Если вы не понимаете, давайте внимательнее, мы говоримA.call ( 参数一)Эта форма предназначена для переименования函数Aсерединаthisда‘参数一’этот объект, тогда давайте посмотримPerson2в теле функцииPerson1.call(this)это заявление, где заявлениеthisОтносится кPerson2этот объект. теперь естьPerson1функциональныйthisперенаправить наPerson2, есть лиPerson2.name='ya LV'.

  • 3. Вызовите функцию

apply,callВсе методы вызывают немедленное выполнение функции, поэтому их также можно использовать для вызова функций. Это то, что мы говорили в начале этого раздела, например,A()а такжеA.call()Обе вызывают функцию A.

  function func() {
      console.log('ya LV');
  }
  func.call();            // ya LV

3.5.1.3 Разница между вызовом и привязкой

существуетEcmaScript5расширен вbindметод, который несовместим с более ранними версиями IE. это иcallОчень похоже, что принятые параметры состоят из двух частей: первый параметр — это объект как контекст функции, а вторая часть параметра — это список, который может принимать несколько параметров.

Отличие между ними заключается в следующих двух моментах.

  1. Отличие 1. Возвращаемое значение bind — это функция
    var name='HELLO'
    var obj = {
      name: 'ya LV'
    }

    function func() {
      console.log(this.name);
    }
    
    //将func的代码拷贝一份,并且永远改变其拷贝出来的函数中的this,为bind第一个参数所指向的对象。把这     份永远改变着this指向的函数返回给func1.
    var func1 = func.bind(obj);
   //bind方法不会立即执行,是返回一个改变上下文this的函数,要对这个函数调用才会执行。
    func1();  //ya LV
   //可以看到,现在这份改变this之后拷贝过来的函数,this的指向永远是bind()绑定的那个,不管之后去call    重新指向对象,func1 都不会改变this的指向。永远!可知,bind比call优先级还高。
    func1.call({name:'CALL'});   //ya LV

    //又从func重新拷贝一份永远改变this指向对象为{name:'LI SI'}这个对象的函数,返回给func2.
    var func2 = func.bind({name:'LI SI'});
    func2();   //LI SI

   //注意,这里是拷贝一份func2(而不是func)的代码,而func2之前已经绑定过去永远改变this的指向了,所以这   里并不去改变!还是会输出原来的最先bind的this指向对象。
    var func3 = func2.bind({name:'ZHANG SAN'});
    func3();   //LI SI

   //上面对func最初的函数进行了多次绑定,绑定后原函数 func 中的 this 并没有被改变,依旧指向全局对象      window。因为绑定bind的过程是拷贝代码的一个过程,而不是在其自身上修改。window.name = HELLO
    func();   //HELLO

bindМетод не выполняется сразу, а возвращает измененный контекстthisпосле функции. И исходная функцияfuncсерединаthisНе изменился, по-прежнему указывает на глобальный объектwindow.

  1. Отличие 2. Использование параметров
 function func(a, b, c) {
      console.log(a, b, c);
  }
  var func1 = func.bind(null,'yaLV');

  func('A', 'B', 'C');            // A B C
  func1('A', 'B', 'C');           // yaLV A B
  func1('B', 'C');                // yaLV B C
  func.call(null, 'yaLV');       // yaLV undefined undefined

callсостоит в том, чтобы принять второй и последующие аргументы какfuncПередаются аргументы метода, иfunc1Фактические параметры методаbindИсходя из параметров посередине, он будет располагаться в заднем ряду. То есть,var func1 = func.bind(null,'yaLV'); bindЕсть два параметра, первый — указатель, а второй — фактический параметр.'yaLV', тогда пустьfuncсерединаa='yaLV', то если он не полон, просто пустьfunc1('A', 'B', 'C'); параметры перечислены по порядку, теперьb='A',c='B', формальные параметры выстроены. То есть выходyaLV A B.

Недоступно в браузерах более ранних версийbindметод, мы также можем реализовать его самостоятельно.

if (!Function.prototype.bind) {
   Function.prototype.bind = function () {
       var self = this,                        // 保存原函数
       context = [].shift.call(arguments), // 保存需要绑定的this上下文
       args = [].slice.call(arguments);    // 剩余的参数转为数组
       return function () {                    // 返回一个新函数
       self.apply(context[].concat.call(args[].slice.call(arguments));
              }
          }
      }

3.1.6 Упражнения и случаи

Упражнение 1

<script type="text/javascript">
     var name='window_dqs';
     var obj={
        name:'obj_dqs',
        showName:function(){
            console.log(this.name);
        }};

     function fn(){
        console.log(this);
     }
     function fn2(){
        this.name='fn_dqs';
     }
	
	//因为obj去调用,this就是obj
     obj.showName();    //obj_dqs  
     //因为借调,而此时借调的对象是this,而this在全局作用域上就是指window,所以找window.name
     obj.showName.apply(this);   //window_dqs
     //因为借调的对象是一个函数对象,那么this就是指函数对象,this.name就是函数名
     obj.showName.apply(fn2);    //fn2
</script>

Упражнение 2

<script type="text/javascript">
 var name='window_dqs';
     function fn(){
        this.name='fn_dqs';
        this.showName=function(){
            console.log(this.name);
        }
        console.log(this);
     }

     function fn2(){
        this.name='fn_pps';
        this.showName=function(){
            console.log(this.name);
        }
        console.log(this);
     }

     var p=new fn();
     fn2.apply(p);
     p.showName();

     var obj={};
     fn2.apply(obj);
     obj.showName();
</script>

оказаться:

    var p=new fn();输出fn { name: 'fn_dqs', showName: [Function] }
    fn2.apply(p);输出fn { name: 'fn_pps', showName: [Function] }
    p.showName();输出fn_pps

	var obj={};
    fn2.apply(obj);输出Object{name: "fn_pps"showName: ƒ ()__proto__: Object··}
    obj.showName();输出fn_pps

Упражнение 3

<script type="text/javascript">
var name='window_dqs';
     var obj={
        name:'json_dqs',
        showName:function(){
            console.log(this.name);
            return function(){
                console.log(this.name);
            }
        }
     }
    var p=obj.showName();
    obj.showName()();
    p.call(obj);
</script>

оказаться:

json_dqs
json_dqs
window_dqs
json_dqs

Интервью Вопрос 1

Фрагмент кода 1:

var name = "The Window";
  var object = {
    name: "My Object",
    getNameFunc: function (){
      return function (){
        return this.name;
      };
    }
  };
  console.log(object.getNameFunc());     //ƒ (){return this.name;}
  console.log(object.getNameFunc()());  //The Window

Фрагмент без замыкания. Вложенные, но не с функцией внешних переменных или функций. это использоватьthisиз.thisЗависит от того, как он называется.

понять: см.object.getNameFunc()является объектом, метод() возвращает функцию, которая еще не была выполнена. jsthisявляется динамической, поэтому функция не выполняется, и неизвестно, что находится в функцииthisКого вы имеете в виду? Затем добавьте () к возвращаемой функции, то естьobject.getNameFunc()(), вызов выполняется, последняя скобка и последняя скобка рассматриваются как две части, передняя часть — это имя функции, а следующая скобка — это вызов. эквивалентноtest(),В настоящее времяthisто естьwindow. Следовательно, вызываемая таким образом функция this относится кwindow, такwindow.name=The Window.

Фрагмент кода два: Для фрагмента 1 мы намерены вывестиMy Object. Итак, как преобразовать, черезthat=thisработать.

var name2 = "The Window";
  var object2 = {
    name2: "My Object",
    getNameFunc: function () {
      var that = this;  //缓存this
      return function () {
        return that.name2;
      };
    }
  };
  console.log(object2.getNameFunc());    //ƒ (){return that.name2;}
  console.log(object2.getNameFunc()()); //My Object

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

Понимание: первый взгляд наobject2.getNameFunc()Возвращает функцию, обратите внимание, что нетthis, при звонкеobject2.getNameFunc, мы выполнили приговорvar that = this, то естьthisДатьthat,В настоящее времяthisозначаетobject2. позвони сноваobject2.getNameFunc()()когда он выполняетсяobject2.getNameFunc()возвращаемая функция».that.name2=object2.name2; по существу является замыканием, использующим внешнюю функциюthatПеременная.

Третий фрагмент кода (модификация второго фрагмента):

var name3 = "The Window";
  var object3 = {
    name3: "My Object",
    getNameFunc: function () {
      return function () {
        return this.name3;
      }.bind(this);
    }
  };
  console.log(object3.getNameFunc());     //ƒ (){return this.name3;}
  console.log(object3.getNameFunc()());   //My Object

Понимание: То же, что и «Фрагмент кода 2», за исключением того, что фрагмент 2 передается черезthat=thisизменитьthisзначение, в то время как фрагмент номер три передаетсяbindсвязыватьthisценность . Смотретьbind(this)здесьthisозначает это предложениеobject3.getNameFunc()вызывающий объектobject3Таким образом,thisуказывает на предыдущий объектobject3, При повторном вызове возвращаемой функцииthis.name3=object3.name3.

Интервью Вопрос 2

<script>
var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log(this.foo);  //bar
        console.log(self.foo);  //bar
        (function() {
          console.log(this.foo);  //undefined  此时的this是window
          console.log(self.foo);  //bar   闭包可以看到外部的局部变量
        }());  //匿名函数自执行,是window上调用这个函数。
    }
};
myObject.func();

//那么如何修改呢?使得在自执行函数中的this.foo就是我们想要的bar呢?
//提供两种方法:
//case1:用call去指向this是谁
var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log(this.foo); // bar
        console.log(self.foo); // bar
        (function() {
            console.log(this.foo);  // bar
            console.log(self.foo); // bar
        }.call(this));  
        //myObject.func();这样调用func(),那么func()中的this就是前面的对象myObject。
    }
};
myObject.func();

//case2:用bind去绑定this,但要注意bind是返回一个函数,故要bind(this)(),后一个括号表示函数调用。把bind(this)将拷贝一份并改变this的指向的函数执行。
var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log(this.foo);  // bar
        console.log(self.foo);  // bar
        (function() {
            console.log(this.foo);  // bar
            console.log(self.foo);  // bar
        }.bind(this)());
    }
};
myObject.func();
</script>

Интервью 3посещениеthisуказывает на: Сложность: элементы массива (подобного массиву) используются как вызовы функций.thisнаправление То есть, если вызывается элемент в вызывающем массиве (подобном массиву), в функции элементаthisэто массив (подобный массиву).

<script>
var length = 10;
function fn(){
    console.log(this.length);
}
var obj = {
    length: 5,
    method: function (fn){  // [fn, 1, 2]  
        fn();  // 10
        arguments[0]();  // 3
    }
};
obj.method(fn, 1, 2);
/*obj.method(fn, 1, 2);传实参fn过去,此时fn拿到函数的地址值拷贝给形参fn,在执行fn()这里调用是相当window调用fn,this指的是window。而不是obj不要感觉是在obj里面就是,迷惑大家的。this的指向永远跟调用方式有关。
*另外,arguments[0]();调用时,这个时候是类数组中的元素调用,那么这时的this是类数组本身,所以,数组.length是不是输出类数组的长度。
*如果是调用数组(类数组)中的元素, 元素函数中的this是这个数组(类数组).为什么呢?看以下两个例子:
* */

//例子1:
var obj = {
    age : 100,
    foo : function (){
        console.log(this);
    }
}
var ff = obj.foo;
ff();  //window
obj.foo(); //{age: 100, foo: ƒ}
obj["foo"]();  //{age: 100, foo: ƒ}
//上面的这个例子没有问题吧。很自然的。

//例子2:
var arr = [
    function (){
        console.log(this);
    },function (){

    }
];
var f = arr[0];
f();  //window

/*arr.0()--类似于这么写把,只是数组不允许这样的语法--*/
 arr[0]();  //输出数组本身:(2) [ƒ, ƒ] 。故验证一句话:如果调用数组(类数组)中的元素时,那么这时的this是数组(类数组)本身。
</script>

3.2 Прототипы и цепочки прототипов

Особенно классическое резюме:

отa.bВы можете увидеть знание масштаба и цепочки масштабов, прототипа и цепочки прототипов. (Подробности см. в 1.1.3 Части 1)

3.2.1 Пять диаграмм для понимания прототипа и цепочки прототипов

Конструктор создает объектСначала мы создаем объект с помощью конструктора:

function Person() {
    
}
var person = new Person();
person.name = 'name';
console.log(person.name) // name

В этом примереPersonэто конструктор, мы используем new для создания экземпляра объектаperson.

Это очень просто, переходите к следующему:【прототип】

Любая функция имеет свойствоprototype, значением этого свойства является объект, и этот объект называется объектом-прототипом этой функции. Но в целом мы ориентируемся только на прототип конструктора. Например:

function Person() {

}
// 虽然写在注释里,但是你要注意:prototype是函数才会有的属性
Person.prototype.name = 'name';

var person1 = new Person();  //person1是Person构造函数的实例
var person2 = new Person();  //person2是Person构造函数的实例
console.log(person1.name) // name
console.log(person2.name) // name

В самом деле, функцияprototypeСвойство указывает на объект, который является прототипом экземпляра, созданного путем вызова конструктора, который являетсяperson1а такжеperson2Прототип. Экземпляру фактически передается невидимое свойство[[proto]]указал на.

Вы можете понять это так: каждый объект JavaScript (nullЗа исключением того, что когда он будет создан, он будет связан с другим объектом, этот объект является тем, что мы называем прототипом, и каждый объект «наследует» свойства от прототипа.

Давайте используем диаграмму для представления отношений между конструкторами и прототипами экземпляров:

prototype
Итак, как мы представляем экземпляры и прототипы экземпляров, т.е.person1 person2а такжеPerson.prototypeОтношения между ними, то мы будем говорить о втором атрибуте:[[proto]]

При использовании конструктора для создания объекта вновь созданный объект будет иметь невидимое свойство [[proto]], указывающее на объект-прототип конструктора. На самом деле каждый объект JavaScript (кроме null) имеет невидимое свойство, называемое [[proto]], которое указывает на прототип объекта.

Чтобы продемонстрировать это, мы можем ввести в Firefox или Google:

function Person() {
    
}
var person1 = new Person();
console.log(person1.__proto__ === Person.prototype); //true

Итак, мы обновляем диаграмму отношений:

proto
Поскольку и объект экземпляра, и конструктор могут указывать на прототип, есть ли у прототипа свойства, указывающие на конструктор или экземпляр? В экземпляре нет смысла, потому что конструктор может генерировать несколько экземпляров, но есть прототипы, которые указывают на конструктор, что касается третьего атрибута: [constructor], каждый прототип имеетconstructorСвойство указывает на связанный конструктор.

Чтобы проверить это, мы можем попробовать:

function Person() {

}
console.log(Person === Person.prototype.constructor); //true

Итак, следующий график обновления:

原型与原型链详解
В общем, мы пришли к:

function Person() {

}
var person1 = new Person();
//对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值(很重要)
console.log(person1.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true

// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person1) === Person.prototype) //true

Зная связь между конструкторами, прототипами экземпляров и экземплярами, давайте поговорим об отношениях между экземплярами и прототипами:【Примеры и прототипы】. При чтении свойств экземпляра, если он не может их найти, он будет искать свойства в прототипе, связанном с объектом.Если он не может их найти, он найдет прототип прототипа и будет найти самый верхний уровень.

Например:

function Person() {

}
//往Person对象原型中添加一个属性
Person.prototype.name = 'name';
//创建一个person1实例对象
var person1 = new Person();
//给创建的实例对象person1添加一个属性
person1.name = 'name of this person1';
//查找person1.name,因为本身实例对象有,那么就找到了自身实例对象上的属性和属性值
console.log(person1.name) // name of this person1
//删除实例对象的属性和属性值
delete person1.name;
//查找属性name,在实例对象自身上找不到,通过proto指向往原型链上找,在原型对象中找到
console.log(person1.name) // name

В этом примере мы установилиperson1изnameсвойство, поэтому мы можем читать какname of this person1, когда мы удаляемperson1изnameсвойство, читатьperson1.name,отperson1Если вы не можете найти его вpersonПрототипperson.__proto__ == Person.prototype, к счастью, мы нашлиname, но что, если вы еще не нашли его? Что такое прототип прототипа?

var obj = new Object();
obj.name = 'name'
console.log(obj.name) // name

Итак, объект-прототип передаетсяObjectСгенерированное конструктором, в сочетании с очень важным предложением, упомянутым ранее, представляет собой почти предложение, которое охватывает знание прототипа и цепочки прототипов, то есть: объект-экземплярprotoконструкторуprototype. То есть,Person.prototypeЭтот объект-прототип (прототип экземпляра) создается с помощьюObjectВыходит новый конструктор, то есть объект-прототип Person.prototypeObject, так что этот экземпляр будет иметьprotoатрибут указывает наObjectОбъект-прототип конструктораObject.prototype.

Вставьте предложение, чтобы подвести итог здесь, можно отодвигать назад и продвигать вперед, то есть: пример проходитprotoЭто свойство указывает на объект-прототип своего конструктора. Итак, давайте обновим диаграмму отношений:

原型与原型链详解
ЭтоObject.prototypeЧто насчет прототипа?nullэм, то естьnull. так нашелObject.prototypeВы можете остановить поиск. Итак, последняя схема:
原型与原型链详解

Это【Сеть прототипов] Что? ЭтоprotoОдним из направлений поиска этого свойства является цепочка прототипов. Цепочная структура взаимосвязанных прототипов на рисунке представляет собой цепочку прототипов, обозначенную синей линией.protoнедвижимость для поиска.

Затем при доступе к свойствам объекта, как найти свойства или методы через цепочку прототипов?Сначала искать в его собственных свойствах и найти результат. Если нет, то следуйтеprotoНайдите эту цепочку, найдите и верните. Если не найдено, вернутьundefined.

3.2.2 Прототип и цепочка прототипов из кода

原型与原型链详解
Разберите картинку выше: сначалаn ,sВсе являются глобальными переменными, а затем объект создается методом объектного литерала. Затем есть конструктор (причина, по которой это конструктор, потому что код позадиnewоперация), этот конструктор будет иметь объявление функции заранее, и когда конструктор будет объявлен, он создастpersonФункциональный объект этого функционального объектаТолькоprototypeАтрибуты, указать наpersonОбъект-прототип функции. Следует отметить, что код внутри еще не был выполнен, но функция была создана при объявлении функции.personфункциональный объект. Back является экземпляром объекта new, новыйnewвне объекта экземпляраP2 P3В памяти будет выделен блок памяти для присвоения ему значения адреса, и теперь будет выполняться код в конструкторе. Так что толькоP2 P3буду иметьname age speakсвойства и методы. эти новыеnewВыходящий объект экземпляра будет иметь невидимое свойствоproto, чтобы указать на объект прототипа. И, наконец, этоpersonОбъект-прототип функции будет указывать наobjectОбъект-прототип , а затем подняться на самом делеnull. Подниматься слой за слоем — это цепочка прототипов, из-за цепочки прототипов у нас будут характеристики наследования.

Несколько замечаний:

  1. Как видно из иллюстрации выше, созданиеP2 P3Экземплярные объекты, хотя и используютPersonконструктор, но после создания объекта этотP2 P3Объект экземпляра на самом деле уже связан сPersonКонструкторы (функциональные объекты) больше не имеют значения,P2 P3экземпляр объекта[[ proto ]]атрибут указывает наPersonОбъект прототипа для конструктора.
  2. При использованииnew Person()Создайте несколько объектов, тогда несколько объектов будут указывать на одно и то же времяPersonОбъект прототипа для конструктора.
  3. Мы можем вручную добавить свойства и методы к этому объекту-прототипу, а затемP2 P3· Эти объекты-экземпляры будут совместно использовать свойства и методы, добавленные в прототип. Другими словами, объект-прототип эквивалентен общедоступной области, и все экземпляры одного и того же класса могут получить доступ к объекту-прототипу.
  4. если мы посетимP2свойство в экземпляре объектаgender, если вP2Если он найден в объекте, он будет возвращен напрямую. еслиP2Если объект не найден, перейдите непосредственно кP2объект[[proto]]Поиск в объекте-прототипе, на который указывает свойство, и возврат, если он найден. (Если он не найден в прототипе, продолжайте искать прототип прототипа --- цепочка прототипов).
  5. При чтении значения свойства объекта: оно будет автоматически искаться в цепочке прототипов
  6. При установке значения свойства объекта: цепочка прототипов не будет просматриваться, если этого свойства нет в текущем объекте, напрямую добавить это свойство и установить его значение. например, черезP2Объекты могут только читать свойства в прототипеnameЗначение прототипа не изменяет свойство в прототипе.nameценность .P2.gender= "male"; не изменяет значение в прототипе, а вP2Добавлено свойство к объектуgender.
  7. Методы обычно определяются в прототипе, а свойства обычно определяются в самом объекте через конструктор.

Также взгляните на три проблемы прототипов и цепочек прототипов:

  1. Объект, на который указывает отображаемый прототип функции, по умолчанию является пустым объектом экземпляра объекта (но объект не удовлетворяется)
console.log(Fn.prototype instanceof Object) // true
console.log(Object.prototype instanceof Object) // false
console.log(Function.prototype instanceof Object) // true
  1. Все функции являются экземплярами функции (включая функцию)
console.log(Function.__proto__===Function.prototype) //true
  1. Объект-прототип объекта является концом цепочки прототипов.
console.log(Object.prototype.__proto__) // null

3.2.3 Исследуйтеinstanceof

instanceofКак это судят?

  • выражение:A instanceof B

  • Если объект-прототип конструктора B (B.prototype) в цепочке прототипов объекта экземпляра A (A.proto.proto·····по цепочке прототипов), возвратtrue, иначе возвратfalse. (Смотри ниже)

    instanceof

  • То есть может быть много объектов в цепочке прототипов объекта экземпляра A, пока один из объектов-прототипов конструктора B является объектом в его цепочке прототипов, он может быть возвращенtrue.

  • И наоборот, то же самое верно, может ли экземпляр объекта A пройтиprotoсвойства (по цепочке прототипов,A.proto.proto·····)оказатьсяB.prototype(объект-прототип B), найдите возвратtrue, возврат не найденfalse.

  • Примечание 1: Описание объектов экземпляра, на самом деле, существует два типа объектов экземпляра. Один из них, что мы часто говоримnewобъекты экземпляра (например, конструкторы)Person , newпублично заявитьp1 p2..., это все объекты-экземпляры), другой - функция, сама функция - тоже экземпляр, естьFunction newот. Но то, что мы обычно называем экземпляром объекта, относится кnewвыходит похоже наp1 p2Экземпляры этих объектов.

FunctionчерезnewСамогенерируемый экземпляр (Function.proto====Function.prototype)

Дело 1:

  //一个构造函数Foo
  function Foo() {  }
  //一个f1实例对象
  var f1 = new Foo()
  //翻译:f1是Foo的实例对象吗?
  //还记得我说过,一个实例对象通过proto指向其构造函数的原型对象上。
  //深入翻译:f1这个实例对象通过proto指向是否可以找到Foo.prototype上呢?
  console.log(f1 instanceof Foo) // true
  //这行代码可以得出,沿着proto只找了一层就找到了。
  console.log(f1.__proto__ === Foo.prototype);   // true
  
  //翻译:f1是Object的实例对象吗?
  //深入翻译:f1这个实例对象通过proto指向是否可以找到Object.prototype上呢?
  console.log(f1 instanceof Object) // true
  //这两行代码可以得出,沿着proto找了两层才找到。事实上,f1.__proto__找到了Foo.prototype(Foo构造函数原型上),再次去.__proto__,找到了Object的原型对象上。见下图。
  console.log(f1.__proto__ === Object.prototype);  // false
  console.log(f1.__proto__.__proto__ === Object.prototype);  // true

js经典原型与原型链的图
Случай 2:

//这个案例的实质还是那句话:一个实例对象通过proto属性指向其构造函数的原型对象上。
//翻译:实例对象Object是否可以通过proto属性(沿着原型链)找到Function.prototype(Function的原型对象)
  console.log(Object instanceof Function) // true
//以上结果的输出可以看到下图,Object.__proto__直接找到一层就是Function.prototype.(Object created by Function)可知Object构造函数是由Function创建出来的,也就是说,Object这个实例是new Function出来的。

  console.log(Object instanceof Object) // true
//很有意思。上面我们已经知道Object这个实例是new Function出来的。也就是Object.proto指向Function.prototype。有意思的是,Function的原型对象又是Object原型对象的一个实例,也就是Function.prototype.proto 指向  Object.prototype .很有意思吧,见下图很更清楚这个“走向”。


  console.log(Function instanceof Function) // true
//由这个可知,可以验证我们的结论:Function是通过new自己产生的实例。                                   	 Function.proto===Function.prototype

  console.log(Function instanceof Object) // true
//Function.proto.proto===Function.prototype (找了两层)

  //定义了一个Foo构造函数。由下图可知,Foo.proto.proto.proto===null  
  function Foo() {}
  console.log(Object instanceof  Foo) // false
//这条语句要验证的是,Object是否可以通过其原型链找到Foo.prototype。
// Object.proto.proto.proto=null  并不会找到Foo.prototype。所以,返回FALSE。

js经典原型与原型链的图
Глядя на картинку выше, возникает еще один вопрос: функции — это объекты. Тогда вы думаете, что функция содержит большие? Или это большой объект? Как показано выше, объекты создаются функциями.(Object created by Function)То есть объект естьnew Functionпринадлежит. Продолжайте переводить, объекты являются экземплярамиFunctionявляется конструктором. Продолжайте переводить, этот экземпляр объекта имеет невидимые свойстваprotoнаправлениеFunctionОбъект-прототип конструктора(Function.prototype). Следовательно, отношение между функцией и объектом таково: функция больше, она содержит объект. Я лично считаю, что это очень важно понять.

3.2.4 Разбор некоторых понятий

  • Все функции имеют особое свойство:
    • prototype: явное свойство прототипа
  • Все объекты экземпляра имеют специальное свойство:
    • __proto__: неявное свойство прототипа
  • Явный прототип против неявного прототипа
    • Прототип функции: он назначается автоматически при определении функции, а значение по умолчанию равно{}, который используется в качестве объекта-прототипа
    • экземпляр объекта__proto__: автоматически добавляется при создании экземпляра объекта и назначается в качестве значения прототипа конструктора.
    • Объект-прототип является родительским объектом текущего объекта-экземпляра.
  • Сеть прототипов
    • Все объекты экземпляра имеют__proto__свойство, указывающее на объект-прототип
    • так через__proto__Атрибуты формируют структуру цепочки ----> цепочка прототипов
    • При поиске свойств/методов внутри объекта js-движок автоматически ищет эту цепочку прототипов.
    • Цепочка прототипов не используется при присвоении значения свойству объекта, она просто работает с текущим объектом.

3.2.5 Упражнения и случаи

Интервью 1:

/*阿里面试题*/
①function Person(){
    ②getAge = function (){
        console.log(10)
    }
    ③return this;
}

④Person.getAge = function (){
    console.log(20);
}

⑤Person.prototype.getAge = function (){
    console.log(30);
}

⑥var getAge = function (){
    console.log(40)
}

⑦function getAge(){
    console.log(50)
}


Q1:Person.getAge() // 20
Q2:getAge() // 40
Q3:Person().getAge() // 10
Q4:getAge() // 10
Q5:new Person().getAge() // 30
Q6:new Person.getAge(); // 20

Общий блок кода ① определяет конструкторPerson②В конструкторе есть необъявленная переменная, эта переменная является ссылочной, а содержимое является значением адреса. Указывает на функциональный объект. И потому, когда не используется строгий режим, он не используется в функцииvarОбъявленный волк станет глобальной переменной. (Обратите внимание, что это не атрибут, а глобальная переменная.) Также обратите внимание, что операторы в ② и ③ не выполняются после анализа здесь. Это зависит от того, реализуется лиnew(используется как конструктор), или отсутствует вызов add() (используется как обычная функция). ③ вернутьthis. этоthisКто еще неизвестно. Нужно понимать js вthisявляется динамическим, поэтому согласно предыдущему разделуthisРезюме позиционируется какthisКто это. ④Person.getAgeявляется типичной формой "object.property(method)", поэтому она даетсяPersonдобавить функциональный объектgetAgeМетоды. Эквивалентно:

  function Person.getAge(){
    console.log(20);
}

Имя функции на самом деле является именем переменной. ⑤Добавлен в прототип конструктораgetAgeМетоды ⑥Здесь также можно присвоить значение адреса глобальной переменной, чтобы она указывала на функциональный объект. Обратите внимание, что здесь переменная var будет объявлена ​​заранее. В отличие от блока кода ②, здесь после синтаксического анализаgetAgeОн уже указывает на объект функции. Можно рассматривать как:

function getAge(){
    console.log(40)
}

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

Q1: Object.PropertyMethod(). Результат кодового блока ④. Q2: Вызов функции в глобальной области видимости. Только этот кодовый блок ⑥ дает результаты. Q3:Person().getAge(). Смотри предыдущую частьPerson(),ПучокPersonКак обычный вызов функции, выполнитеPersonтело функции и глобальная переменнаяgetAgeопределить и переопределить, т.е.Person()Блок ② выполняется для покрытия работы блока ⑥. Вернулся к этому, согласноPerson()Этот метод вызова, зная, что этоwindow. Итак, это «window.gerAge()», поскольку он перезаписывается, результатом выполнения этой строки кода является блок кода ②. Q4:getAge()эквивалентноwindow.getAge(); Тем не менее результат предыдущего оператора, блок кода ② выдает результат. Q5:new Person()Сначала посмотрите на эту часть, то есть экземпляр new выходит, вы можете думать об этом какp1,Такp1.getAge(); p1ЯвляетсяPersonпример,p1есть невидимые[[proto]]атрибут, указывающий наPersonобъект-прототип. Такp1.getAge (),Сейчасp1Если найдёте сами, если не найдёте, следуйте по цепочке прототипов (protoУказывая на цепочку), чтобы найти ее, чтобы найти прототип, потому что кодовый блок ⑤ имеет эффект. Q6:new Person.getAge();можно поставитьPerson.getAgeкак объект, перейти кnewОн похож на наш обычныйvar p1=new Person(); такая операция, поэтому ставимPerson.getAgeДумайте об этом как о конструктореnewЭто. Исходя из приведенного выше понимания кодового блока ④, его можно рассматривать как такую ​​функцию, поэтому результатом является результат, сгенерированный кодовым блоком ④.

Вопрос интервью 2:

function A () {

  }
  A.prototype.n = 1;
  var b = new A();
  A.prototype = {
    n: 2,
    m: 3
  };
  var c = new A();
  console.log(b.n, b.m, c.n, c.m);  //1 undefined 2 3
//见下图:

js经典原型与原型链的图
Вопрос интервью 3: Вопрос о постоянном задании

//与上题的区别在于如何理解a.x的执行顺序
<script>
var a = {n: 1};
var b = a;
a.x = a = {n: 2};  //先定义a.x再去从右往左赋值操作。
console.log(a.x);  // undefined  对象.属性 找不到 是返回undefined  变量找不到则报错!
console.log(b);  // {n :1, x : {n : 2}}
</script>
//见下图分析

在内存结构

Вопрос интервью 4:

//构造函数F
  function F (){};
  Object.prototype.a = function(){
    console.log('a()')
  };
  Function.prototype.b = function(){
    console.log('b()')
  };
  //new一个实例对象f
  var f = new F();

  f.a();  //a()
  f.b();  //报错,找不到
  F.a();  //a()
  F.b();  //b()

2.3 Контекст выполнения и стек контекста выполнения

2.3.1 Переменный подъем и функциональный подъем

Подъем объявления переменной

  • пройти черезvarОпределенные (объявленные) переменные, к которым можно получить доступ перед оператором определения
  • стоимость:undefined
  • Примечание. Если переменная объявлена ​​без ключевого слова var, она не объявляется поднятой.
console.log(c); //报错,c is not defined.
    console.log(b); //undefined
    var b=0;
    c=4;
    console.log(c); //4 意外的全局变量->在ES5的严格模式下就会报错。
    console.log(b); //0  

подъем объявления функции

  • Функции, объявленные через function, можно вызывать непосредственно перед
  • значение: определение функции (объект)
  • Примечание 1: Существует тонкая разница между объявлением функции и выражением функции.Должно быть ясно, что это два типа определений функций в Javascript, и эти две концепции бок о бок. Другими словами, есть два способа определить функцию: один — это объявление функции, а другой — функциональное выражение.
  • Примечание 2: Функциональные выражения не объявляют подъем.

Есть переменные для улучшения, затем есть функции

Дело номер один:

var a = 3;
  function fn () {
    console.log(a);  //undefined
    var a = 4
  }
  fn();

//上面这段代码相当于
 var a = 3;
  function fn () {
      var a;
      console.log(a);  //undefined
      a = 4
  }
  fn();

Случай 2:

  console.log(b) //undefined  变量提升
  fn2() //可调用  函数提升
  fn3() //不能调用,会报错。  fn3是一个函数表达式,并不会函数提升,实际上他是变量提升。

  var b = 3
  function fn2() {
    console.log('fn2()')
  }
  var fn3 = function () {
    console.log('fn3()')
  }

Вопрос: Как происходит подъем переменных и подъем функций?Ответ: Поскольку существует процесс предварительной обработки глобального контекста выполнения и контекста выполнения функции. Итак, давайте узнаем о контексте выполнения в следующем разделе.

2.3.2 Контекст выполнения

Классификация кода (местоположение)

  • глобальный код
  • функциональный (местный) код

Контекст выполнения делится на глобальный контекст выполнения и контекст выполнения функции.

глобальный контекст выполнения

  • Шаг 1: Перед выполнением глобального кодаwindowОпределяется как объект глобального контекста выполнения (виртуальный)
  • Шаг 2: Предварительная обработка глобальных данных (сбор данных)
    • varОпределенная глобальная переменная ==> значениеundefinedи добавить какwindowсвойства
    • functionобъявленная глобальная функция ==> присваивание(fun), добавлено какwindowМетоды
    • this==> задание(window)
  • Шаг 3. Запустите глобальное выполнение кода.
     //全局执行上下文
     console.log(a1);  //undefined
     console.log(a2);  //undefined
     a2();   //也会报错,a2不是一个函数
     console.log(a3);  //ƒ a3() {console.log('a3()')}
     console.log(a4)   //报错,a4没有定义
     console.log(this); //window

     var a1 = 3;
    //函数表达式,实际上是变量提升。而不是函数提升。
     var a2 = function () {
       console.log('a2()')
     };
     function a3() {
       console.log('a3()')
     }
     a4 = 4;

контекст выполнения функции

  • Шаг 1: Перед вызовом функции и подготовкой к выполнению тела функции создайте соответствующий объект контекста выполнения функции (виртуальный, существующий в стеке. Стек будет разделен на стек глобальных переменных и стек локальных переменных. Стек локальных переменных можно понимать как закрытое пространство памяти.Хотя написанный нами код не может получить доступ к этому объекту, синтаксический анализатор использует его за кулисами при обработке данных.)
  • Шаг 2: Предварительная обработка локальных данных (сбор данных)
    • Переменная формального параметра ==> присвоение (фактический параметр) ==> добавлен атрибут в качестве контекста выполнения
    • arguments==> присваивание (список аргументов), добавленное как свойство контекста выполнения
    • varОпределенные локальные переменные ==>undefined, добавленный как свойство контекста выполнения
    • functionобъявленная функция ==> присваивание(fun), метод, добавленный в качестве контекста выполнения
    • this==> присваивание (функция вызова объекта)
  • Шаг 3: Начните выполнять код тела функции
 //函数执行上下文
 function fn(a1) {
    console.log(a1);  //2  实参对形参赋值
    console.log(a2);  //undefined  函数内部局部变量声明提升
    a3();     //a3()  可调用  函数提升
    console.log(arguments);  //类数组[2,3]
    console.log(this);  //window

    var a2=3;
    function a3() {
      console.log("a3()");
    }
  }
  fn(2,3);  //执行,不执行不会产生函数执行上下文

Жизненный цикл глобального контекста выполнения и контекста выполнения функции
Глобальный: генерируется перед подготовкой к выполнению глобального кода, умирает при обновлении/закрытии страницы.
Функция: генерируется при вызове функции и умирает при ее выполнении.

2.3.3 Стек контекста выполнения

  • Понимание процесса стека контекста выполнения:
  1. Перед выполнением глобального кода механизм JS создаст стек для хранения и управления всеми объектами контекста выполнения.
  2. в глобальном контексте выполнения (window) определяется, добавляем его в стек (push the stack)
  3. После создания контекста выполнения функции добавляем его в стек (push)
  4. После выполнения текущей функции объект на вершине стека удаляется (извлекается).
  5. Когда весь код выполнен, остается только стекwindow
<script type="text/javascript">
                            //1. 进入全局执行上下文
  var a = 10;
  var bar = function (x) {
    var b = 5;
    foo(x + b)              //3. 进入foo执行上下文
  };
  var foo = function (y) {
    var c = 5;
    console.log(a + c + y)
  };
  bar(10);   //2. 进入bar函数执行上下文(注:函数执行上下文对象在函数调用时产生,而不是函数声明时产生)
</script>

Весь процесс описанной выше ситуации генерирует 3 контекста выполнения. Однократный вызов функции создает контекст выполнения Если в последней строке вышеприведенного кодаbar(10), позвоните еще разbar(10), то будет сгенерировано 5 контекстов. потому что первая полоса(10)создает контекст функции вbarвызов функцииfoo, и создается контекст выполнения функции. Тогда позвони еще разbar(10), то же, что и выше, сгенерирует два контекста, добавляя до 4 контекстов выполнения функции. Наконец добавьтеwindowКонтекст глобальной переменной , всего пять контекстов выполнения.

递归
执行上下文
Примечание к рисунку 1: этот процесс немного похож на идею возврата рекурсивных функций. В стеке сначалаwindowглобальный контекст, затем выполнитеbar()положитbarКонтекст выполнения функции помещается в стек.barвызыватьfoo,ПучокfooКонтекст выполнения функции помещается в стек,fooПосле того, как функция будет выполнена и выпущена, она поместитfooконтекст выполнения функцииpop(посадочная дистанция). постепенноbarЗаконченный,popвнеbarКонтекст выполнения функции, только в концеwindowконтекст.

Примечание 2: Предположим ситуацию:f1()функция вызоветf2()а такжеf3()функция. Итак, сколько контекстов может быть достигнуто в стеке в текущий момент? Ан: когдаf1()Выполнить, позвонит первымf2(), после звонка,f2()После завершения миссии ее жизненный цикл заканчивается, поэтому стек освободит ее и выполнит.f3(), поэтому в стеке не более трех контекстов.f3() f1() window.

Примечание 3: Предположим другую ситуацию:f1()функция вызоветf2(), f2()будет вызванf3()функция. Итак, сколько контекстов может быть достигнуто в стеке в текущий момент? Ан: когдаf1()Выполнить, позвонит первымf2(),воплощать в жизньf2()звонить, когдаf3(), поэтому стек может достигать 4 контекстов.f3() f2() f1() window .

2.3.4 Упражнения и случаи

Вопрос интервью 1: Стек контекста выполнения

  1. Какой вывод по порядку?
  2. Сколько контекстов выполнения генерируется во всем процессе?
<script type="text/javascript">
  console.log('global begin: '+ i); //undefined 变量提升
  var i = 1;
  foo(1);
  function foo(i) {
    if (i == 4) {
      return;
    }
    console.log('foo() begin:' + i);
    foo(i + 1);
    console.log('foo() end:' + i);
  }
  console.log('global end: ' + i)  //1 全局变量i,其他的函数中的i当执行结束后就销毁了。

Результаты:

&emsp;global begin: undefined
  foo() begin:1
  foo() begin:2
&emsp;foo() begin:3
&emsp;foo() end:3
&emsp;foo() end:2
&emsp;foo() end:1
&emsp;global end: 1

Всего генерируется 5 контекстов: Анализ показан на картинке ниже, я нарисовал ее очень четко. Этот рисунок занял 12 минут. Главное - нажать и вытолкнуть стек. Прежде чем вытолкнуть стек, вернитесь к исходной функции. Контекст выполнения этой функции все еще существует. Если еще есть невыполненные операторы, она будет выполнена в это время. Когда оставшиеся операторы выполнены, жизненный цикл контекста выполнения этой функции заканчивается, и стек освобождается. Рассмотрим пример нашего рекурсивного вызова для нахождения факториала, идея та же.

递归思想

Вопрос интервью 2: Подъем переменных и подъем функций (контекст выполнения)

function fn(a){
    console.log(a);  // 输出function a的源码,a此时是函数a
    var a = 2;
    function a(){

    }
    console.log(a);  // 2
}
fn(1);

Пункт проверки: Заявление заранее Сложность: сначала функция

В начале вызова будет создана локальная переменная a (поскольку a — формальный параметр), а затем ей будет присвоено значение 1 фактического параметраa,a= 1Почти одновременно, а затем в одно мгновение начните обработку подъема переменных и подъема функций внутри функций. В настоящее время,aПоскольку подъем функций сталa = function(){}Все вышеперечисленные процессы являются процессами предварительной обработки контекста выполнения функции. Далее идет код, формально выполняющий внутреннюю функцию.console.log(a);На данный момент выходом является исходный код функции.ƒ a(){}Оператор вывода в конце выведетa = 2.

Тестовый вопрос 1: [Проверить точки знаний] Сначала предварительно обработать переменные, затем предварительно обработать функции

function a() {} //函数提升
  var a;  //变量提升
  //先预处理变量, 后预处理函数。也就是,函数提升会覆盖变量提升。
  console.log(typeof a); //function

Тестовый вопрос 2: [Проверить очки знаний] Предварительная обработка переменных в операторе (можете ли вы найти b в окне, независимо от того, есть ли значение)

 if (!(b in window)) {
    var b = 1;  
    //在ES6之前没有块级作用域,所以这个变量b 相当于window的全局变量
  }
  console.log(b); //undefined

Тестовый вопрос 3: [Проверить точки знаний] предварительная обработка, последовательное выполнениеЯ думаю, что этот вопрос довольно хорош. Запутать точку зрения читателя. Конечно, еще раз подчеркивается, что вопросы собеседования специально формулируются для экзамена, и некоторые из них могут не использоваться в реальной разработке. Но главная функция заключается в глубоком понимании.

var c = 1;
  function c(c) {
    console.log(c);
    var c = 3;
  }
  c(2); //报错。 c is not a function

  //这个题包含了变量和函数声明提升的问题,就是等价于以下的代码:
  var c;  //变量提升
  function c(c) {  //函数提升,覆盖变量提升
    console.log(c);
    var c = 3;   //函数内部的局部变量(在栈内存的封闭内存空间里,外面看不到)
  }
  c=1;//开始真正执行代码var c = 1
  console.log(c);
  c(2);  //c is not a function  c是一个变量,值为number类型的数值.怎么可以执行?

2.4 Объем и цепочка объемов

2.4.1 Область применения

1. Поймите:

  • Область действия: это "сайт", область кода, которая определяется во время кодирования и не изменится (см. диаграмму ниже). Область действия запускается, когда определяется функциональная переменная. Конец выполнения завершает конец жизненного цикла области.
  • Цепочка областей: внутренняя структура из нескольких вложенных областей, используемая для поиска переменных (см. диаграмму ниже).

2. Классификация:

  • глобальная область
  • объем функции
  • js не имеет блочной области (но вES6Понятно! )

3. Функция

  • Область: изолированные переменные, переменные с одним и тем же именем могут быть определены в разных областях, не вызывая конфликтов. Переменные с одинаковыми именами в разных областях видимости не будут конфликтовать. Например, есть переменная в глобальнойb, то могут ли в теле функции быть переменные?b, конечно, это то, что разделяет переменные.
  • Цепочка областей видимости: найти переменные

Дайте случай:

var a = 10,
    b = 20
  function fn(x) {
    var a = 100,
      c = 300;
    console.log('fn()', a, b, c, x)
    function bar(x) {
      var a = 1000,
        d = 400
      console.log('bar()', a, b, c, d, x)
    }

    bar(100)
    bar(200)
  }
  fn(10);

Выходной результат:

fn() 100 20 300 10
bar() 1000 20 300 400 100
bar() 1000 20 300 400 200

4. Схема прицела следующая:

作用域的图解

2.4.2 Область применения и контекст выполнения

1. Отличие 1

  • Вне глобальной области каждая функция создает свою собственную область, которая определяется при определении функции. не при вызове функции.
  • Глобальный контекст выполнения создается после определения глобальной области видимости и непосредственно перед выполнением кода js.
  • Контекст выполнения функции создается при вызове функции до выполнения кода тела функции.

2. Отличие 2

  • Область видимости статична, она существует до тех пор, пока определена функция, и не изменится.
  • Контекст выполнения является динамическим, создается при вызове функции и автоматически освобождается при завершении вызова функции (не собирается сборщиком мусора).

3. Контакт

  • Контекст выполнения (объект) подчинен области, в которой он находится.
  • Глобальный контекст ==> Глобальный объем
  • Контекст функции ==> соответствующий объем функции

4. Объем и контекст выполнения проиллюстрированы следующим образом:

作用域与执行上下文图解

2.4.3 Цепочка областей применения

1. Понять

  • Цепочка, образованная сферой множественных подчиненных отношений, ее направление снизу вверх (изнутри наружу)
  • Когда вы ищете переменную, вы просматриваете цепочку областей видимости

2. Найдите правило поиска для переменной

  • а. Найдите соответствующий атрибут в контексте выполнения под текущей областью, если есть прямой возврат, иначе введите б
  • б) Найдите соответствующий атрибут в контексте выполнения области верхнего уровня, если есть прямой возврат, иначе введите c
  • C. Выполните ту же операцию 2 снова, пока глобальная область, если она не найдена, не выдаст исключение не найдено

3. Схема цепочки областей применения следующая:

作用域链的图解

2.4.4 Упражнения и случаи

Вопрос интервью 1: Объем

<script type="text/javascript">
  var x = 10;
  function fn() {
    console.log(x);  //10
  }
  function show(f) {
    var x = 20;
    f();
  }
  show(fn);
</script>

Помните: область действия фиксируется сразу после написания кода, она не меняется. Сколько областей создается?n+1. nсколько функций,1означаетwindow. Поиск переменных — это поиск по скоупу, а скоуп определяется изначально, и не имеет никакого отношения к тому, где он вызывается. См. иллюстрацию:

作用域
Вопрос интервью: изучение области действия и поиска в цепочке областей действия

<script type="text/javascript">
  var fn = function () {
    console.log(fn) //output: ƒ () {console.log(fn)}
  }
  fn()

  var obj = {
    fn2: function () {
     console.log(fn2) //报错,fn2 is not defined 
     console.log(this.fn2)//输出fn2这个函数对象  
    } 
  }
  obj.fn2()
</script>

Причина ошибки: потому что он сначала ищется в рамках этой анонимной функции, не может найти его, переходите на верхний уровень, чтобы найти его глобально, если он не найден, сообщается об ошибке. Находитьfn2ищется по размаху! выходfn2Причина для этого функционального объекта: если вы хотите найтиobjАтрибутыfn2, затем используйтеthis.fn2(), пусть используетthisэтот указатель указывает наobj,существуетobjнайти этот объектfn2Атрибуты.

Вопрос интервью 3: изучение подразумеваемого значения непрерывного присваивания

(function(){
    var a = b = 3;
})();

console.log(a);  // 报错 a is not defined
console.log(b);  // 3

Понимание: Во-первых, задания выглядят справа налево.b = 3Поскольку var отсутствует, это эквивалентно добавлению в глобальную область видимостиb, назначенный как3. Сейчас. смотреть впередvar a=Часть a имеет var, тогда a является локальной переменной, помещенной в закрытое пространство памяти стека.var a=b,bявляется переменной, переменной примитивного типа данных. Значение содержимого в его памяти является значением базового типа данных, поэтому скопируйте копию вa. в локальных переменныхa=3

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

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

Обратите внимание на расширение, здесь только в нестрогом режиме, будетbкак глобальная переменная. Если он находится в строгом режиме в ES5, будет сообщено об ошибке.

Интервью 4. Рассмотрим проблему возвращаемого значения

function foo1(){
    return {
        bar: "hello"
    }
}

function foo2(){
    return
    {
        bar: "hello"
    }
}

console.log(foo1()); // 返回一个对象 {bar:'hello'}
console.log(foo2());  // undefined

Объяснение: потому чтоfoo2функцияreturnТочка с запятой отсутствует.При разборе js движка знание принципа компиляции видно на собрании по лексическому анализу.returnПо умолчанию добавляется точка с запятой, поэтому последний объект вообще не выполняется и вообще не заботится о нем. Так когдаreturnвернулся, когдаundefined.

Интервью 5: Область видимости функциональных выражений

<script>
console.log(!eval(function f() {})); //false
var y = 1;
if (function f(){}){
    y += typeof f;
}
console.log(y);  // 1undefined
</script>

2.5 Закрытие

2.5.1 Введение замыканий

Code 1:

<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">
  var btns = document.getElementsByTagName('button')
  //遍历加监听
  for (var i = 0,length=btns.length; i < length; i++) {
    var btn = btns[i]
    btn.onclick = function () {
      alert('第'+(i+1)+'个')
    }
  }
</script>

Результат вывода: независимо от того, какой из них нажатbutton, весь вывод "4-й". потому чтоforЦикл выполняется один раз, ноbtn.onclickЭто ожидание запуска пользовательского события, поэтому на этот разiда3, Всегда выводить "4th". Немного подробностей: в процессе, сколькоi? Одинi,iЭто глобальная переменная. Обработка событийной модели: Когда событие запускается, событие отвечает на это взаимодействие, добавляя новое задание (функция обратного вызова) в конец очереди заданий, что является самой простой формой js об асинхронном программировании. События хорошо подходят для простых взаимодействий, но объединение в цепочку нескольких отдельных асинхронных вызовов может быть громоздким, поскольку вам нужно отслеживать объект события для каждого события (например, приведенного вышеbtn) Также вы должны убедиться, что все обработчики событий могут быть привязаны до того, как событие будет запущено в первый раз. Например, еслиbtnсуществуетonclickНажмите перед привязкой, тогда ничего не произойдет. Таким образом, хотя события полезны в ответ на взаимодействие с пользователем или аналогичные низкочастотные функции, они по-прежнему недостаточно гибки для более сложных потребностей. Итак, из этого примера следует не просто понимание обхода плюс прослушивание/закрытие и т.д. Отсюда также можно объяснить проблему объектов событий и механизмов событий. Итак, в ES6 будетpromiseи асинхронные функции для более сложных операций.

Код 2对象.属性спастиi:

<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">
  var btns = document.getElementsByTagName('button')
  //遍历加监听
  for (var i = 0,length=btns.length; i < length; i++) {
    var btn = btns[i]
    //将btn所对应的下标保存在btn上(解决方式1)
    btn.index = i
    btn.onclick = function () {
      alert('第'+(this.index+1)+'个')
    }
  }
</script>

На этот раз результат, который мы хотим, какой из них нажатьi ,buttonПросто выведите первое число.

Код 3 через область блока ES6let:

<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">
  var btns = document.getElementsByTagName('button')
  //遍历加监听
  for (let i = 0,length=btns.length; i < length; i++) {  //(解决方式二)
    var btn = btns[i]  
    btn.onclick = function () {
      alert('第'+(i+1)+'个')
    }
  }
</script>

Введите область блока в ES6, используйтеletВот и все.

Код 4 использует замыкания для решения

<button>测试1</button>
<button>测试2</button>
<button>测试3</button>
<script type="text/javascript">
  var btns = document.getElementsByTagName('button')
  //利用闭包
  for (var i = 0,length=btns.length; i < length; i++) {  //这里的i是全局变量
    (function (j) {  //这里的j是局部变量
      var btn = btns[j]
      btn.onclick = function () { 
        alert('第'+(j+1)+'个')   //这里的j是局部变量
      }
    })(i); //这里的i是全局变量
  }
</script>

forВ цикле есть две функции,btn.clickЭта анонимная функция является замыканием. Он обращается к переменным внешней функции. Сколько замыканий генерируется? 3 замыкания (несколько замыканий генерируются, когда внешняя функция выполняется несколько раз). Каждое замыкание имеет переменныеj, хранятся отдельноj=0,j=1,j=2ценность . Поэтому такого эффекта можно добиться. Причина проблемы раньше заключалась в том, что используется глобальная переменная i и используется то же значение i. Закрытие было выпущено? Нет, это было всегда. Мы знаем, что освобождение закрытия, то есть пусть ссылочная переменная, указывающая на внутреннюю функцию, будетnullВот и все. Но в это времяbtn.onclickНа эту внутреннюю функцию (анонимную функцию) всегда ссылаются, поэтому ее закрытие не будет выпущено. Должны ли закрытия быть освобождены? Не следует. потому что одна страница из одногоbuttonдолжен существовать все время. В процессе отображения страницыbuttonВсегда ассоциироваться с этим закрытием. делать каждый щелчокbutton1Сразуalert(1-й) такой результат. невозможно позволитьbuttonНажмите один раз, и это не удастся. Итак, предположим, вы хотите освободить эти замыкания, пустьbtn.onclick=nullВот и все. Роль замыканий? расширить локальную переменнуюjжизненный цикл.

2.5.2 Понимание замыканий

1. Как генерировать замыкания?

  • Замыкание создается, когда вложенная внутренняя (дочерняя) функция ссылается на переменную (функцию) вложенной внешней (родительской) функции.
  • Примечание 1: Если внешняя функция имеет переменныеb, Но не относится к внутренним функциямb, замыкание не будет сгенерировано.

2. Что такое замыкание?
Замыкание — это функция, которая имеет доступ к переменным в области видимости другой функции.
Это можно понять как:
Контейнер, содержащий эту локальную переменную (не обязательно объект, например объект)
На него ссылается внутренний объект функции
Как узнать, существует ли замыкание? В конце концов, нужно определить, был ли объект функции удален сборщиком мусора.

  • Сделайте хром отлаживаемым, чтобы увидеть наличие замыканий
  • Понимание 1: замыкания — это вложенные внутренние функции (большинство людей)
  • Понимание 2: Объекты, содержащие ссылочные переменные (функции) (очень мало)
  • Если вы понимаете второе, обратите внимание: замыкания существуют во вложенных внутренних функциях.
  • Разговорный язык: Прежде всего, поймите, что замыкание — это, по сути, объект, объект, хранящийся во внутренней функции, и этот объект хранит переменные, на которые ссылаются.
  • В среде фонового выполнения область замыкания включает в себя собственную область, область, содержащую функцию, и глобальную область.

3. Условия генерации замыканий?

  • вложение функций
  • Внутренняя функция ссылается на данные (переменную/функцию) внешней функции.
  • Выполнение внешних функций (внутренние функции могут не выполняться)

Дело 1:

 function fn1 () {
      var a = 2
      var b = 'abc'
      function fn2 () { //执行函数定义就会产生闭包(不用调用内部函数)
        console.log(a)  //引用了外部函数变量,若里面没有引用任何的外部函数变量(函数)则不会产生闭包
      }
      // fn2() 内部函数可以不执行,也会产生闭包。只要执行了内部函数的定义就行。但若是函数表达式呢?
    }
    fn1(); //外部函数要执行哦,否则不会产生闭包

Случай 2:

 function fun1() {
      var a = 3
      var fun2 = function () {
        console.log(a)
      }
    }
    fun1()
  //这样子通过函数表达式定义函数,若没有在里面调用内部函数,则不会产生闭包。

Случай 3:

  function fun1() {
      var a = 3
      var fun2 = function () {
        console.log(a)
      }
      fun2()
    }
    fun1()
  //这样子通过函数表达式定义函数,但在里面调用了内部函数,那么这个情况是可以产生闭包的。

Выражения функций отличаются от объявлений функций. Для объявлений функций требуется имя, а для выражений функций — нет. Функциональное выражение без имени также называется анонимной функцией. Анонимную функцию иногда называют лямбда-функцией. Атрибут имени анонимной функции представляет собой пустую строку.

Вышеприведенные случаи приведены только для облегчения понимания. Настоящего приложения нет, давайте поговорим о том, где на самом деле можно применить замыкания.

2.5.3 Общие замыкания

  1. Использовать функцию как возвращаемое значение другой функции
  2. Передать функцию в качестве аргумента вызову другой функции

Случай 1: Использование функции в качестве возвращаемого значения другой функции

 function fn1() {
    var a = 2
    function fn2() {
      a++
      console.log(a)
    }
    return fn2
  }
  var f = fn1()
  f() // 3
  f() // 4

Глубокое понимание: Вопрос 1: Есть ли закрытие? Ответ: Условие первое, вложенность функций. внешняя функцияfn1, внутренняя функцияfn2, Условие один выполнено Условие два, внутренняя функция ссылается на данные (переменная/функция) внешней функции.aЭто переменная данных внешней функции. Условие 2 выполнено. Условие третье, выполнить внешнюю функцию.var f = fn1()один из нихfn1()Выполняется ли она, внешняя функция выполняется. назначить наfПеременная из-за внешней функцииfn1Возвращает функцию после выполнения с глобальными переменнымиfдля хранения его значения адреса. Условие третье выполнено. Таким образом, создается замыкание.

Вопрос 2: Сколько замыканий генерируется? производит замыкание. Мы знаем из предыдущего раздела: выполнение определения функции сгенерирует замыкание. Затем, чтобы выполнить определение функции, необходимо выполнить только внешнюю функцию, потому что после выполнения внешней функции будет объект контекста функции, и объявление функции будет расширено, то есть определение функции будет казнен. Итак, сколько раз за это время выполняется внешняя функция? это не один раз. Внешняя функция выполняется один раз, то есть функция объявляется один раз заранее, и операция выполнения определения функции выполняется один раз, поэтому генерируется только одно замыкание. Также можно сделать вывод, что при многократном выполнении внешней функции генерируется несколько замыканий. Неважно, сколько раз выполняется внутренняя функция (при условии, что замыкание может быть сгенерировано)

Вопрос 3: Почему вызов внутренней функции может прочитать последнее значение a? Из результатов видно, чтоf() ,f()Вызывает ли он внутреннюю функцию дважды из результата вывода,aКаждый раз выводится последнее значение. Это показывает, что при выполнении внутренней функцииaне исчез. Помните об этом, это суть замыканий.

Вопрос 4: Тогда, если я сейчас в конце приведенного выше кода (отдельно выводить3,4заявление) продолжаем присоединяться

  var h =fn1();
  h(); 
  f(); 

Что будет выведено в это время? Ан:h()будет выводить3. f()будет выводить5. потому что:var h = fn1()При повторном выполнении h получает объект функции возвращаемого значения (внутренняя функция), то есть в это время генерируется новое замыкание. При вызове внутренней функцииh(), будет сгенерирован новый объект контекста функции,aЗначение будет записано от начального значения. при звонкеf()В это время он все еще находится в состоянии предыдущего закрытия, объем не исчез, поэтому он все еще изменен на исходной основе.aстоимость.

Случай 2. Передача аргументов функции вызову другой функции (★★★)

 function showDelay(msg, time) {
    setTimeout(function () {
      alert(msg)
    }, time)
  }
  showDelay('my name is ly', 2000)

Этот пример показывает, что нам не нужно использовать замыканияreturnвыйди. Пока есть ссылка на объект функции.returnЕсли это так, то я буду использовать переменную, чтобы получить ее снаружи и сослаться на нее. Но я использую таймер.Модуль таймера работает в отдельных потоках в браузере.Функция обратного вызова таймера сохраняется и управляется модулем таймера.

Глубокое понимание: Вопрос 1: Есть ли закрытие? Ответ: Условие первое, вложенность функций. внешняя функцияshowDelay, функция обратного вызова внутренней функции таймера, первое условие выполнено, второе условие, внутренняя функция ссылается на данные (переменная/функция) внешней функции.msgЭто переменная данных внешней функции, которая используется в функции обратного вызова. Уведомление,timeНет,timeОна по-прежнему используется во внешней функции, а переменная времени внешней функции не используется во внутренней функции. Потому чтоmsgПеременная удовлетворяет второму условию. Условие третье, выполнить внешнюю функцию.showDelay('my name is ly', 2000)Выполнено, внешняя функция выполнена. Но учтите, что callback-функция не объявляет повышение, поэтому ей приходится ждать.2000ms后触发进行调用回调函数。这个时候内部函数才执行。条件三满足(这个类似于函数表达式情况,如果是函数表达式,那么不仅仅要外部函数要执行,内部函数表达式定义的函数也要有执行,只有这样才会出现闭包。如果是函数声明定义的函数,那么就会有函数执行上下文去创建,函数提升,故在执行函数定义的时候就会出现闭包)。 Таким образом, создается замыкание.

2.5.4 Эффект закрытия

1. Используйте локальные переменные внутри функции, чтобы они оставались в памяти после выполнения функции (продлевая жизненный цикл локальных переменных). Первоначально жизненный цикл локальных переменных заключается в том, начинает ли функция выполняться до тех пор, пока выполнение не завершится, и локальные переменные автоматически уничтожаются. Однако жизненный цикл локальных переменных можно продлить за счет замыканий, а локальные переменные внутри функций могут продолжать жить в памяти после выполнения функции. То есть через закрытие, конкретный внутренний механизм выглядит следующим образом.

2. Пусть внешняя часть функции работает (читает и записывает) с данными (переменными/функциями) внутри функции. Первоначально внутри функции можно пройти через цепочку областей видимости, чтобы найти данные (переменные/функции) изнутри наружу, и можно получить доступ к внешней части функции. Но обратное невозможно.Может ли внешняя функция получить доступ к внутренним данным (переменным/функциям)?

 function fun1() {
    var a='hello world'
  }
  console.log(a); //报错 a is not defined  
  //函数外部不能访问函数内部的数据

Следовательно, данные внутри функции не могут быть доступны извне функции. Однако к данным внутри функции можно получить доступ через замыкание. Каков конкретный внутренний механизм? Смотри ниже.

//详解闭包的作用(重要)
function fn1() {
    var a = 2
    function fn2() {
      a++
      console.log(a)
    }
    function fn3() {
      a--
      console.log(a)
    }
    return fn3
  }
  
  var f = fn1()
  f() // 1
  f() // 0

Вопрос 1. Функцииfn1()Существуют ли локальные переменные, объявленные внутри функции, после выполнения?An: 一般是不存在, 存在于闭包中的变量才可能存在。 картинаfn2 fn3Переменная автоматически разрушается. Поскольку жизненный цикл локальных переменных в функции является процесс с начала выполнения функции к концу выполнения. что нравитсяfn2Этот функциональный объект также будет утилизирован механизмом сборки мусора, поскольку нет переменных, на которые можно было бы ссылаться (указывать).fn2функциональный объект. ноfn3Этот объект все еще существует, основная причина в том, что операторvar f = fn1(); fn( )Вернутьfn3и присвоить его глобальной переменной f, то глобальная переменная f будет указывать на него, так что этоfn3Этот объект функции не будет механизмом сборки мусора. Но когда я отвечаю на этот вопрос, говорят, что в закрытии единственной переменной, которая может присутствовать. Почему это может быть? Потому что, если я поставлю заявлениеvar f = fn1();изменить наfn1(), в настоящее время по-прежнему нет переменной для ссылки, поэтому она все еще будет переработана в это время. Смотри ниже.

在这里插入图片描述
Итак, мы обнаружили, что замыкания будут существовать всегда? Это жизненный цикл замыканий, о котором мы поговорим в следующем разделе. Скажи это заранее, т.fn3Объекты-функции всегда будут иметь ссылки, а замыкания всегда будут существовать. В этот момент я простоf=null,В настоящее времяfn3функциональные объекты неfСсылка, поэтому она будет переработана механизмом сборки мусора, поэтому это закрытие в это время умирает.

Вопрос 2: Во внешней функции можно напрямую доступ к локальным переменным внутри функции?О: Нет, но мы можем позволить внешнему манипулировать им через замыкания.

2.5.5 Жизненный цикл затворов

  1. Генерируется: Генерируется, когда определение вложенной внутренней функции выполняется (не вызывается) ---> Для внутренних вложенных функций, объявленных с помощью functions.
  2. Умирает: когда вложенные внутренние функции становятся мусорными объектами.
function fn1() {
    //此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)
    var a = 2
    function fn2 () {
      a++
      console.log(a)
    }
    return fn2
  }
  var f = fn1()
  f() // 3
  f() // 4
  f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)

2.5.6 Применение замыканий

Приложение закрытия:

  • Модульность: инкапсулируйте некоторые данные и функции, которые манипулируют данными, и предоставьте некоторые варианты поведения. (Конкретно в этом разделе) Отсюда можно вывести четыре модульные идеи.
  • Обход цикла плюс мониторинг (описано во введении замыканий в 2.5.1)
  • JS-фреймворк (jQuery) активно использует замыкания

Одно из применений замыканий: определение модулей JS

  1. js, чтобы иметь определенную функцию

  2. Инкапсулировать все данные и функции внутри функции (частной) (внутри функции будет концепция области действия и цепочки областей видимости, данные внутри функции являются частными и недоступны снаружи.)

  3. Предоставлять только один объект, содержащий n методов (представляя несколько вариантов поведения) или функций (представляя одно поведение)

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

Пользовательский модуль JS один:

function myModule() {
  //私有数据
  var msg = 'Hello world';
  //操作数据的函数
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }

  //向外暴露对象(给外部使用的方法)
  return {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
}

//怎么使用?在html页面中
  var module = myModule()
  module.doSomething()
  module.doOtherthing()

Пользовательский модуль JS два:

(function () {
  //私有数据
  var msg = 'My atguigu'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }

  //向外暴露对象(给外部使用的方法)
  window.myModule2 = {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
})();

//怎么使用?在html页面中
  myModule2.doSomething()
  myModule2.doOtherthing()
//这种自定义模块相对而言更好,因为不需要先调用外部函数,直接使用 myModule2.doSomething()更加方便。

2.5.7 Недостатки и решения замыканий

1. Недостатки

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

2. Решить

  • Можно ли обойтись без замыканий?
  • своевременное освобождение
<script type="text/javascript">
  function fn1() {
    var arr = new Array[100000]
    function fn2() {
      console.log(arr.length)
    }
    return fn2
  }
  var f = fn1()
  f()
  //这里是有闭包的,arr一直没有释放,很占内存。
  //如何解决?很简单。
  f = null //让内部函数成为垃圾对象-->回收闭包

</script>
  • понимать:

    • Закрытие происходит, когда вложенная внутренняя функция ссылается на переменную внешней функции.
    • Благодаря инструменту Chrome мы знаем, что замыкание по сути является объектом во внутренней функции, и этот объект содержит свойства переменной, на которую ссылаются.
  • эффект:

    • Продлить время жизни локальных переменных
    • Пусть внешняя часть функции работает с локальными переменными внутри
  • Написать программу закрытия

  function fn1() {
    var a = 2;
    function fn2() {
      a++;
      console.log(a);
    }
    return fn2;
  }
  var f = fn1();
  f();
  f();
  • Приложение закрытия:

    • Модульность: инкапсулируйте некоторые данные и функции, которые работают с данными, и предоставьте некоторые варианты поведения.
    • Обход цикла плюс мониторинг
    • JS-фреймворк (jQuery) активно использует замыкания
  • недостаток:

    • Переменные могут слишком долго занимать память
    • Может вызвать утечку памяти
    • решить:
      • Своевременный выпуск:f = null;// Сделать объект внутренней функции мусорным объектом

2.6.9 Упражнения и случаи

Интервью 1: Изучение замыканий

function foo(){
    var m = 1;
    return function (){
        m++;
        return m;
    }
}

var f = foo();  //这会形成一个闭包  (调用一次外部函数)
var f1 = foo();  //这会形成一个闭包 (调用一次外部函数)
/*不同闭包有不同作用域。同一个闭包可以访问其最新的值。--这句话知识一个表面现象,结合上面的案例去发现深入的步骤,这个过程是如何执行的?*/
console.log(f()); // 2
console.log(f1()); // 2
console.log(f()); // 3
console.log(f()); // 4

Интервью 2: Знания, связанные с закрытием

<script>
function fun(n, o){
    console.log(o);  //实则就是输出闭包中的变量值,n是闭包引用的变量。延长的是n的变量生命周期。
    return {
        fun: function (m){
            return fun(m, n);
        }
    }
}
/*注意以上代码段是有闭包的,return fun(m,n)中的n是用到了外部函数的变量n*/

//测试一:
var a = fun(0);  // undefined
a.fun(1); // 0  执行这里实则是产生了新的闭包,但没有变量去指向这个内部函数产生的闭包故马上就消失啦。
a.fun(2); // 0
a.fun(3); // 0
/*最后三行语句一直用的闭包是fun(0)产生的闭包*/

//测试二: 
var b = fun(0).fun(1).fun(2).fun(3); //undefined 0 1 2
/*产生了四个闭包,也就是外部函数fun(n,o)调用过4次。*/

// 测试三:
var c = fun(0).fun(1); // undefined 0
c.fun(2)  // 1
c.fun(3)  // 1
/*最后两行语句一直用的闭包是fun(0).fun(1)产生的闭包,故其语句*/

</script>

Интервью 3: Напишите функцию, которая делает оба следующих вызова правильными

console.log(sum(2,3));   // Outputs 5
console.log(sum(2)(3));  // Outputs 5

Отвечать:

<script>
function sum(){
    if(arguments.length == 2){
        return arguments[0] + arguments[1];
    }else if(arguments.length == 1){
        var first = arguments[0];
        return function (a){
            return first + a;
        }
    }
}
</script>


Этот документ является оригинальным Lu YA, его можно воспроизводить, перепечатывать, но, пожалуйста, сохраняйте исходную ссылку, укажите источник.
Статья публикуется только на CSDN и Nuggets впервые:
Домашняя страница CSDN: https://blog.csdn.net/LY_code
Домашняя страница Наггетс: https://juejin.cn/user/3667626521532855
Если есть какие-то ошибки, сообщайте о них вовремя, учитесь вместе и добивайтесь прогресса вместе. Благодарю. 😝😝😝