Привет, ты действительно это понимаешь?

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

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

Другие статьи можно нажать: GitHub.com/Иветт Л.А. Ю/Б…

Сначала ответьте, пожалуйста, на первый вопрос: как точно определить, на что это указывает? 【Часто задаваемые вопросы в интервью】

[Картинка взята из Интернета, захвачена и удалена]

Посмотрите на другой вопрос, какое значение выводит консоль? [Операционная среда браузера]

var number = 5;
var obj = {
    number: 3,
    fn1: (function () {
        var number;
        this.number *= 2;
        number = number * 2;
        number = 3;
        return function () {
            var num = this.number;
            this.number *= 2;
            console.log(num);
            number *= 3;
            console.log(number);
        }
    })()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(window.number);

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

Ведь потратить час-другой на то, чтобы полностью в этом разобраться, очень стоящее дело, не так ли?

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

Зачем это изучать?

Во-первых, зачем нам это изучать?

  1. Это используется очень часто, и если мы этого не понимаем, нам будет очень сложно смотреть на чужой код или исходный код.
  2. На работе этим злоупотребляю, но не понимаю на что это указывает, что приводит к проблемам, но не знаю где проблема. [В компании я помог как минимум 10 разработчикам справиться с этой проблемой]
  3. Разумное использование этого позволяет нам писать краткий и повторно используемый код.
  4. Высокочастотные вопросы интервью, ответ не очень хороший, извините, поверните направо, когда будете выходить, и не отправляйте его.

Независимо от того, какова цель, мы должны сделать это знание ясным.

Хорошо пойдем!

что это?

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

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

  1. привязка по умолчанию
  2. неявное связывание
  3. жесткий переплет
  4. новый переплет

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

привязка по умолчанию

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

function sayHi(){
    console.log('Hello,', this.name);
}
var name = 'YvetteLau';
sayHi();

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

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

Но если он запускается в среде узла, результатом будет Hello, undefined, потому что имя в узле не связано с глобальным объектом.

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

неявное связывание

Вызов функции запускается для объекта, то есть в месте вызова находится контекстный объект. Типичная форма — XXX.fun() Давайте посмотрим на фрагмент кода:

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
person.sayHi();

Напечатанный результат: Hello, YvetteLau.

Функция sayHi объявлена ​​внешне и строго говоря не принадлежит человеку, но при вызове sayHi вызывающая локация будет использовать контекст человека для ссылки на функцию, а неявная привязка будет ссылаться на this в вызове функции (то есть в функции sayHi в этом примере). this) привязан к этому объекту контекста (то есть человеку в этом примере)

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

function sayHi(){
    console.log('Hello,', this.name);
}
var person2 = {
    name: 'Christina',
    sayHi: sayHi
}
var person1 = {
    name: 'YvetteLau',
    friend: person2
}
person1.friend.sayHi();

Результат: Здравствуйте, Кристина.

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

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

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
var Hi = person.sayHi;
Hi();

Результат: Привет, Вильям.

Почему это?Привет прямо указывает на ссылку sayHi.При звонке не имеет отношения к человеку.Для таких проблем советую просто запомнить такой формат:XXX.fn();Если перед fn( ), то это точно не неявная привязка.

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

function sayHi(){
    console.log('Hello,', this.name);
}
var person1 = {
    name: 'YvetteLau',
    sayHi: function(){
        setTimeout(function(){
            console.log('Hello,',this.name);
        })
    }
}
var person2 = {
    name: 'Christina',
    sayHi: sayHi
}
var name='Wiliam';
person1.sayHi();
setTimeout(person2.sayHi,100);
setTimeout(function(){
    person2.sayHi();
},200);

Результат:

Hello, Wiliam
Hello, Wiliam
Hello, Christina
  • Первый результат легко понять. В функции обратного вызова setTimeout используется привязка по умолчанию. В нестрогом режиме выполняется глобальный объект.

  • Разве второй вывод не сбивает с толку? Когда мы говорим XXX.fun(), это в fun указывает на XXX, почему в этот раз это не так! Почему?

    На самом деле здесь мы можем понять это так: setTimeout(fn, delay){ fn(); }, что эквивалентно присвоению переменной person2.sayHi и, наконец, выполнению переменной. не имеет ничего общего с person2 .

  • Хотя третий элемент также находится в обратном вызове setTimeout, мы можем видеть, что это неявная привязка, используемая person2.sayHi(), так что это указывает на person2, который не имеет ничего общего с текущими отношениями.

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

явная привязка

Явную привязку лучше понять, то есть через call, apply, bind явно указать объект, на который указывает this. (Примечание: в «Javascript, которого вы не знаете» привязка объясняется только как жесткая привязка)

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

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
var Hi = person.sayHi;
Hi.call(person); //Hi.apply(person)

Вывод: Привет, YvetteLau, потому что жесткая привязка используется для явной привязки этого к человеку.

Итак, означает ли использование жесткого связывания отсутствие потери связывания при неявном связывании? Очевидно, что это не так, не верьте, продолжайте смотреть вниз.

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
var Hi = function(fn) {
    fn();
}
Hi.call(person, person.sayHi); 

Вывод Hello, William Причина очень проста, Hi.call(person, person.sayHi) связывает это с этим в Hi. Но при выполнении fn это эквивалентно прямому вызову метода sayHi (помните: person.sayHi был присвоен fn, и неявная привязка также теряется), а значение this не указывается, что соответствует значению по умолчанию привязка.

Теперь надеемся, что привязка не потеряется, как нам это сделать? Очень просто, при вызове fn тоже жестко привязывается.

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
var Hi = function(fn) {
    fn.call(this);
}
Hi.call(person, person.sayHi);

В этот момент выходным результатом будет: Hello, YvetteLau, потому что person привязан к this в функции Hi, а fn привязывает этот объект к функции sayHi. В это время это в sayHi указывает на объект человека.

На данный момент революция почти победила, давайте посмотрим на последнее правило связывания: новое связывание.

новая привязка

В отличие от C++, в javaScript нет классов. В javaScript конструкторы — это просто функции, которые вызываются при использовании оператора new. Эти функции ничем не отличаются от обычных функций. Они не принадлежат классу и не могут быть инстанцированы. . Любую функцию можно вызвать с помощью new, поэтому здесь нет конструктора, а есть только «вызов конструкции» функции.

Вызов функции с помощью new автоматически делает следующее:

  1. Создайте пустой объект, это в конструкторе указывает на этот пустой объект
  2. Этот новый объект выполняется соединением [[prototype]]
  3. Выполняются методы конструктора, свойства и методы добавляются к объекту, на который ссылается этот
  4. Если в конструкторе не возвращается никакой другой объект, верните этот новый созданный объект, в противном случае верните объект, возвращенный в конструкторе.
function _new() {
    let target = {}; //创建的新对象
    //第一个参数是构造函数
    let [constructor, ...args] = [...arguments];
    //执行[[原型]]连接;target 是 constructor 的实例
    target.__proto__ = constructor.prototype;
    //执行构造函数,将属性或方法添加到创建的空对象上
    let result = constructor.apply(target, args);
    if (result && (typeof (result) == "object" || typeof (result) == "function")) {
        //如果构造函数执行的结构返回的是一个对象,那么返回这个对象
        return result;
    }
    //如果构造函数返回的不是一个对象,返回创建的新对象
    return target;
}

Поэтому, когда мы используем new для вызова функции, новый объект привязывается к this функции.

function sayHi(name){
    this.name = name;
	
}
var Hi = new sayHi('Yevtte');
console.log('Hello,', Hi.name);

Выходным результатом является Hello, Yevtte, потому что на шаге var Hi = new sayHi('Yevtte'); это в sayHi будет привязано к объекту Hi.

обязательный приоритет

Мы знаем, что здесь четыре обязательных правила, но что, если несколько правил применяются одновременно?

Очевидно, нам нужно знать, какой метод привязки имеет более высокий приоритет.Приоритет этих четырех методов привязки:

новая привязка > явная привязка > неявная привязка > привязка по умолчанию

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

обязательное исключение

Во всем есть исключения, как и в этом правила.

Если мы передаем null или undefined в качестве объекта привязки this в вызов, применение или привязку, эти значения будут игнорироваться при вызове, и фактически применяются правила привязки по умолчанию.

var foo = {
    name: 'Selina'
}
var name = 'Chirs';
function bar() {
    console.log(this.name);
}
bar.call(null); //Chirs 

Результатом является Chirs, потому что в это время фактически применяются правила привязки по умолчанию.

стрелочная функция

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

(1) Объект this в теле функции наследует this внешнего блока кода.

(2) Его нельзя использовать как конструктор, то есть нельзя использовать новую команду, иначе будет выброшена ошибка.

(3) Вы не можете использовать объект arguments, которого нет в теле функции. Если вы хотите использовать его, вы можете вместо этого использовать остальные параметры.

(4) Команду yield нельзя использовать, поэтому стрелочные функции нельзя использовать в качестве функций генератора.

(5) Стрелочные функции не имеют собственного this, поэтому такие методы, как call(), apply() и bind(), не могут использоваться для изменения направления this.

Хорошо, давайте посмотрим, что это за функция стрелки?

var obj = {
    hi: function(){
        console.log(this);
        return ()=>{
            console.log(this);
        }
    },
    sayHi: function(){
        return function() {
            console.log(this);
            return ()=>{
                console.log(this);
            }
        }
    },
    say: ()=>{
        console.log(this);
    }
}
let hi = obj.hi();  //输出obj对象
hi();               //输出obj对象
let sayHi = obj.sayHi();
let fun1 = sayHi(); //输出window
fun1();             //输出window
obj.say();          //输出window

Так почему это? Если вы скажете, что this в функции стрелки является объектом, в котором она определена, результат будет не таким, как вы ожидали.Согласно этому определению, this in say должен быть obj.

Проанализируем приведенные выше результаты выполнения:

  1. obj.hi(); соответствует правилу неявной привязки this, this привязано к obj, поэтому вывод obj легко понять.
  2. hi(); Этот шаг выполняет стрелочную функцию. Стрелочная функция наследует this от предыдущей кодовой базы. Мы только что обнаружили, что this предыдущего слоя — это obj. Очевидно, здесь это obj.
  3. Выполнение sayHi(); Этот шаг также хорошо понятен. Ранее мы упоминали, что эта неявная привязка теряется. В настоящее время выполняется привязка по умолчанию, и это указывает на окно глобального объекта.
  4. fun1(); На этом шаге выполняется стрелочная функция.Если, согласно предыдущему пониманию, это указывает на объект, где определена стрелочная функция, то это, очевидно, не имеет смысла. Хорошо, легко понять, что this функции стрелки унаследовано от this внешней кодовой базы. Мы только что проанализировали базу внешнего кода, это указывает на окно, так что вывод здесь — это окно.
  5. obj.say(); выполняет функцию стрелки. Этого нет в текущем блоке кода obj. Вы можете только посмотреть и найти глобальный this, который указывает на окно.

Вы сказали, что эта функция стрелки является статической?

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

я бы сказал: нет

var obj = {
    hi: function(){
        console.log(this);
        return ()=>{
            console.log(this);
        }
    },
    sayHi: function(){
        return function() {
            console.log(this);
            return ()=>{
                console.log(this);
            }
        }
    },
    say: ()=>{
        console.log(this);
    }
}
let sayHi = obj.sayHi();
let fun1 = sayHi(); //输出window
fun1();             //输出window

let fun2 = sayHi.bind(obj)();//输出obj
fun2();                      //输出obj

Можно видеть, что fun1 и fun2 соответствуют одной и той же стрелочной функции, но результат у них разный.

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

Суммировать

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

Напомним, исходный вопрос.

1. Как точно определить, на что это указывает?

  1. Вызывается ли функция в новом (новая привязка), если да, то она привязывается к вновь созданному объекту.
  2. Независимо от того, вызывается ли функция вызовом, применением или использованием привязки (т.е. жесткой привязки), если это так, то она привязана к указанному объекту.
  3. Вызывается ли функция в объекте контекста (с неявной привязкой), и если да, то она привязана к этому объекту контекста. Обычно obj.foo()
  4. Если ничего из перечисленного выше, то используются привязки по умолчанию. Привязывается к undefined, если в строгом режиме, в противном случае к глобальному объекту.
  5. Если в качестве объекта привязки this для вызова, применения или привязки передается null или undefined, эти значения будут игнорироваться при вызове, и фактически применяются правила привязки по умолчанию.
  6. Если это стрелочная функция, this стрелочной функции наследует this внешнего блока кода.

2. Анализ процесса исполнения

var number = 5;
var obj = {
    number: 3,
    fn: (function () {
        var number;
        this.number *= 2;
        number = number * 2;
        number = 3;
        return function () {
            var num = this.number;
            this.number *= 2;
            console.log(num);
            number *= 3;
            console.log(number);
        }
    })()
}
var myFun = obj.fn;
myFun.call(null);
obj.fn();
console.log(window.number);

Давайте проанализируем процесс выполнения этого кода.

  1. При определении obj выполняется замыкание, соответствующее fn, функция в нем возвращается, а при выполнении кода в замыкании новую привязку применить явно нельзя (ключевое слово new не появляется), и нет жесткая привязка (вызов не появляется). , apply, ключевое слово bind), есть ли неявная привязка? Очевидно, что нет, если XX.fn() отсутствует, то явно не применяется неявная привязка, поэтому здесь применяется привязка по умолчанию В нестрогом режиме она привязывается к окну (среде выполнения браузера). [Что здесь легко спутать, так это то, что this указывает на obj, мы должны обратить внимание, если только это не стрелочная функция, иначе это и лексическая область видимости — две разные вещи, мы должны это помнить]
window.number * = 2; //window.number的值是10(var number定义的全局变量是挂在window上的)

number = number * 2; //number的值是NaN;注意我们这边定义了一个number,但是没有赋值,number的值是undefined;Number(undefined)->NaN

number = 3;          //number的值为3
  1. myFun.call(null);Как мы уже говорили ранее, первый параметр call передает null, и вызывается привязка по умолчанию;
fn: function(){
    var num = this.number;
    this.number *= 2;
    console.log(num);
    number *= 3;
    console.log(number);
}

При выполнении:

var num = this.number; //num=10; 此时this指向的是window
this.number * = 2;     //window.number = 20
console.log(num);      //输出结果为10
number *= 3;           //number=9; 这个number对应的闭包中的number;闭包中的number的是3
console.log(number);   //输出的结果是9
  1. obj.fn(); применяется неявная привязка, и это в fn соответствует obj.
var num = this.number;//num = 3;此时this指向的是obj
this.number *= 2;     //obj.number = 6;
console.log(num);     //输出结果为3;
number *= 3;          //number=27;这个number对应的闭包中的number;闭包中的number的此时是9
console.log(number);  //输出的结果是27
  1. Последний шаг console.log(window.number), выходной результат 20

Итак, результат в группе:

10
9
3
27
20

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

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

Спасибо, что потратили свое драгоценное время на чтение этой статьи. Если эта статья дала вам небольшую помощь или вдохновение, не скупитесь на лайки и звезды. Это, безусловно, самая большая мотивация для меня двигаться вперед.GitHub.com/Иветт Л.А. Ю/Б…

Спасибо за указание, добавьте ссылку следующим образом:

  • «Книги по JavaScript, которых вы не знаете»
  • Документация ES6 — стрелочные функции (голодание 6. ruanyifeng.com/#docs/womenforms…)
  • Классическая статья с вопросами об интервью, если у вас есть ссылка, вы можете оставить мне сообщение.

Подпишитесь на официальный аккаунт и присоединитесь к группе технического обмена