На что это указывает? Узнайте, прочитав это!

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

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

что это

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

function func() {
  this.name = "小小飞";

  console.log(this);    // 看一下this是啥
}

Этот метод очень прост, просто добавьте к нему атрибут имени, мы скопируем этот метод в инструмент отладки Chrome, чтобы увидеть результат:

image-20200226153116734

На приведенном выше рисунке мы напрямую вызываемfunc(), обнаружил, что это указывает на окно,nameСвойства добавляются в окно. Давайте изменим метод вызова, мы заменим его наnew func()звонить:

image-20200226153812137

Мы видим результат двухfunc {name: "小小飞"}, один объект, возвращаемый нашим новым, а другой — консоль в методе. Два значения совпадают, что указывает на то, что на этот раз метод указывает на объект, возвращаемый new, а не на окно в предыдущем примере. Это потому, что когда вы используетеnewПри вызове метода метод фактически используется как конструктор, в это время this указывает на новый объект.

Ниже мы объясним следующие ситуации

При вызове с новым это указывает на объект из нового

Это правило на самом деле является частью объектной ориентации JS, JS использует очень извилистый способ поддержки объектной ориентации. Когда вы используете new для выполнения функции, функция становится классом, ключевое слово new возвращает вам экземпляр класса, а функция действует как конструктор. Как объектно-ориентированный конструктор, он должен иметь возможность инициализировать свойства для экземпляра, поэтому в конструкторе должен быть какой-то механизм для работы с созданным экземпляром, и этот механизм таков. Пусть this указывает на сгенерированный экземпляр, чтобы манипулировать экземпляром через this.Более подробное объяснение объектной ориентации в JS см. в этой статье.

Эта особенность этого также имеет несколько замечательных применений. Функцию можно вызвать напрямую или с помощью new.Если я хочу, чтобы пользователь вызывал ее только через new, есть ли способ? Следующее изображение взято из исходного кода Vue:

image-20200226160322071

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

Когда нет явного вызывающего объекта, это указывает на окно

На самом деле это упоминается в первом примере, где нет явного вызывающего объекта, this указывает на window. Давайте поговорим о другом примере здесь, о функции внутри функции, на кого это указывает?

function func() {
  function func2() {
    console.log('this:', this);   // 这里的this指向谁?
  }

  func2();
}

Выполним его и увидим:

Выполнить напрямую:

image-20200226162443098

Выполнить с новым:

image-20200226162626673

Мы обнаружили, что независимо от того, выполняется ли он напрямую или выполняется с использованием new, значение this указывает на окно. Это легко понять, когда выполняется напрямую, потому что нет четкого звонящего, то это, естественно, window.Следует отметить, что при использовании нового используется только новыйfuncЭто конструктор, его это указывает на новый объект, и эта функция в нем еще указывает наwindow.

Когда есть явный вызывающий абонент, это указывает на вызывающего абонента

См. этот пример:

var obj = {
  myName: "小小飞",
  func: function() {
    console.log(this.myName);
  }
}

obj.func();    // 小小飞

Приведенный выше пример легко понять, потому что вызывающий объект — obj, так что это в func указывает на obj,this.myNameто естьobj.myName. На самом деле, это можно комбинировать с предыдущим.Когда нет явной вызывающей стороны, неявной вызывающей стороной является окно, поэтому часто говорят, чтоэто всегда указывает на вызывающего абонента.

Немного изменим этот пример:

var myName = "大飞哥";

var obj = {
  myName: "小小飞",
  func: function() {
    console.log(this.myName);
  }
}

var anotherFunc = obj.func;

anotherFunc();   // 输出是啥?

Вывод здесь должен быть "Большой Фей Гэ", потому что хотяanotherFuncЗа телом функции следуетobj.funcТо же самое, но среда выполнения у него другая.По сути у него нет ясной звонилки, или звонилка оконная. здесьthis.myNameФактическиwindow.myName, то есть «Большой брат».

Давайте снова изменим этот пример:

let myName = "大飞哥";

var obj = {
  myName: "小小飞",
  func: function() {
    console.log(this.myName);
  }
}

var anotherFunc = obj.func;

anotherFunc();   // 输出是啥?

На этот раз мы просто поставили первыйvarизменился наlet, но наш вывод становитсяundefined. Это связано с тем, что переменные, определенные с помощью let и const, не станут свойствами окна даже в самом внешнем слое, только переменные, определенные с помощью var, станут свойствами окна.

Стрелочные функции не связывают это

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

var myName = "大飞哥";

var obj = {
  myName: "小小飞",
  func: () => {
    console.log(this.myName);
  }
}

var anotherFunc = obj.func;

obj.func();      // 大飞哥
anotherFunc();   // 大飞哥

в приведенном выше кодеobj.func()Выход тоже "Большой Фэй Гэ", т.к.objСтрелочная функция объявляется при ее создании.В это время стрелочная функция будет искать текущую область, потому чтоobjЭто объект, а не область действия, поэтому областью действия здесь является окно, а это — окно.

Давайте посмотрим на другой пример:

var myName = "大飞哥";

var obj = {
  myName: "小小飞",
  func: function () {
    return {
      getName: () => {
        console.log(this.myName);
      }
    }
  }
}

var anotherFunc = obj.func().getName;

obj.func().getName();      // 小小飞
anotherFunc();   // 小小飞

Оба выхода - "Сяо Сяофэй",obj.func().getName()Вывод «Xiao Xiaofei» легко понять, здесь функция стрелки находится вobj.func()объявлено в возвращаемом значении , на данный момент это фактическиfunc()это, потому что он былobjназывается, так что это указывает на obj.

так почемуanotherFunc()Выход тоже "Сяо Сяофэй"? Это потому чтоanotherFunc()Результат этого на самом деле вanotherFuncОпределяется при назначении:

  1. var anotherFunc = obj.func().getName;На самом деле, он был выполнен первымobj.func()
  2. воплощать в жизньobj.func()когдаgetNameОбъявлены стрелочные функции
  3. В это время this функции стрелки должно быть this текущей области, то естьfunc()внутри этого
  4. func()потому что это былоobjпозвонить, так что это указывает наobj
  5. передачаanotherFunc, собственно, это уже давно определено, т.е.obj, окончательный выводobj.myName.

Давайте посмотрим на стрелочную функцию в конструкторе. Мы упоминали функцию в конструкторе ранее. Когда она вызывается напрямую, это указывает на окно, но что, если эта функция является стрелочной:

var myName = "大飞哥";

function func() {
  this.myName = "小小飞";

  const getName = () => {
    console.log(this.myName);
  }

  getName();
}

new func(); // 输出啥?

Вывод здесь «Xiao Xiaofei», принцип тот же, когда объявляется функция стрелки, это определяется как this текущей области, вотfuncразмах, за которым следуетfuncЭто также указывает на новый экземпляр. Если вы не используете new, а вызываете его напрямую, this здесь указывает на window.

В обратном вызове события DOM это указывает на объект, привязанный к событию.

function func(e) {
  console.log(this === e.currentTarget);   // 总是true
  console.log(this === e.target);          // 如果target等于currentTarget,这个就为true
}

const ele = document.getElementById('test');

ele.addEventListener('click', func);

currentTargetОтносится к объекту DOM, к которому привязано событие,targetОтносится к объекту, вызвавшему событие. В обратном вызове события DOM это всегда указывает наcurrentTarget, если объект, который запускает событие, оказывается объектом, который связывает событие, то естьtarget === currentTarget, это также будет указывать наtarget. Если обратный вызов является стрелочной функцией, это this в области действия при объявлении стрелочной функции.

В этом строгом режиме не определено

function func() {
  "use strict"
  console.log(this);
}

func();   // 输出是undefined

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

<html>
  ...
  <script>
    "use strict"
    console.log(this);     // window
  </script>
  ...
</html>

можно ли это изменить

это можно изменить,callа такжеapplyВы можете изменить это, ES6 также добавил новыйbindфункция.

Измените это, используя вызов и примените

const obj = {
  myName: "大飞哥",
  func: function(age, gender) {
    console.log(`我的名字是${this.myName}, 我的年龄是${age},我是一个${gender}`);
  }
}

const obj2 = {
  myName: "小小飞"
}

obj.func.call(obj2, 18, "帅哥");  // 我的名字是小小飞, 我的年龄是18,我是一个帅哥

Обратите внимание, что имя вывода выше — «Xiao Xiaofei», т. е.obj2.myName. обычный прямой звонокobj.func()Имя вывода должно бытьobj.myName, то есть «Большой брат». но если вы используетеcallДля звонка первый параметр звонка указывается вручнуюthis. мы указываем его какobj2, то есть внутри функцииthis.myNameНа самом деле этоobj2.myName.

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

obj.func.apply(obj2, [18, "帅哥"]);   // 我的名字是小小飞, 我的年龄是18,我是一个帅哥

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

Используйте привязку, чтобы изменить это

bindЭто метод, введенный ES5, и он также может изменить это, но вызов его не приведет к немедленному выполнению самого метода, а вернет новый метод с измененным этим:

const obj = {
  myName: "大飞哥",
  func: function(age, gender) {
    console.log(`我的名字是${this.myName}, 我的年龄是${age},我是一个${gender}`);
  }
}

const obj2 = {
  myName: "小小飞"
}

const func2 = obj.func.bind(obj2);   // 返回一个this改为obj2的新方法
func2(18, "帅哥");    // 我的名字是小小飞, 我的年龄是18,我是一个帅哥

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

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

const func3 = obj.func.bind(obj2, 18);   // 注意我们这里已经传了一个年龄参数
func3("帅哥");    //注意这里只传了性别参数,年龄参数已经在func3里面了,输出还是:我的名字是小小飞, 我的年龄是18,我是一个帅哥

Напишите звонок сами

Зная функцию вызова, напишем вызов сами:

Function.prototype.myCall = function(...args) {
  // 参数检查
  if(typeof this !== "function") {
    throw new Error('Must call with a function');
  }

  const realThis = args[0] || window;
  const realArgs = args.slice(1);
  const funcSymbol = Symbol('func');
  realThis[funcSymbol] = this;   // 这里的this是原方法,保存到传入的第一个参数上

  //用传入的参数来调方法,方法里面的this就是传入的参数了
  const res = realThis[funcSymbol](...realArgs); 

  delete realThis[funcSymbol];  // 最后删掉临时存储的原方法

  return res;  // 将执行的返回值返回
}

Напишите заявку сами

Метод применения очень похож на метод вызова, разница только в том, что он принимает параметры вызова:

Function.prototype.myApply = function(...args) {
  if(typeof this !== "function") {
    throw new Error('Must call with a function');
  }

  const realThis = args[0] || window;
  // 直接取第二个参数,是一个数组
  const realArgs = args[1];        
  const funcSymbol = Symbol('func');
  realThis[funcSymbol] = this;   

  const res = realThis[funcSymbol](...realArgs); 

  delete realThis[funcSymbol]; 

  return res; 
}

Напишите привязку самостоятельно

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

Function.prototype.myBind = function(...args) {
  if(typeof this !== "function") {
    throw new Error('Must call with a function');
  }

  const _func = this;    // 原方法
  const realThis = args[0] || window;   // 绑定的this
  const otherArgs = args.slice(1);    // 取出后面的参数作为新函数的默认参数

  return function(...args2) {   // 返回一个方法
    return _func.apply(realThis, [...otherArgs,...args2]);  // 拼接存储参数和新参数,然后用apply执行
  }
}

Суммировать

  1. Это вне функции, то есть это в глобальной области видимости указывает на окно.
  2. Это внутри функции всегда относится к непосредственному вызывающему объекту. Если прямого вызывающего объекта нет, подразумеваемый вызывающий объект — это window.
  3. Используйте new для вызова функции, эта функция является конструктором. Это в конструкторе является мостом для связи с объектом экземпляра, он указывает на объект экземпляра.
  4. Это внутри стрелочной функции определяется при ее объявлении, точно так же, как это в ее текущей области.
  5. В обратном вызове события DOM это указывает на объект, связанный с событием (currentTarget), а не на объект, вызвавший событие (target). Конечно, они могут быть одинаковыми. Если обратный вызов представляет собой функцию со стрелкой, обратитесь к предыдущей, это this области действия, когда она объявлена.
  6. В строгом режиме это внутри функции указывает на undefined, а это вне функции (глобальная область) по-прежнему указывает на окно.
  7. call и apply могут изменить это.Эти два метода будут немедленно выполнять исходный метод.Разница между ними заключается в том, что форма параметра отличается.
  8. bind также может изменить это, но он не будет выполнять это немедленно, а вернет функцию, которая модифицирует это.

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

Добро пожаловать, чтобы обратить внимание на мой общедоступный номербольшой фронт атакиПолучите высококачественные оригиналы впервые~

Цикл статей "Передовые передовые знания":Nuggets.IM/post/5oh3FFC…

Адрес GitHub с исходным кодом из серии статей «Advanced Front-end Knowledge»:GitHub.com/Денис — см....