Як ключове слово "це" працює у функції?


248

Я просто натрапив на цікаву ситуацію в JavaScript. У мене є клас із методом, який визначає кілька об'єктів за допомогою об'єктно-буквальних позначень. Усередині цих об'єктів використовується thisвказівник. З поведінки програми я дійшов висновку, що thisвказівник посилається на клас, до якого викликався метод, а не на об'єкт, що створюється літералом.

Це здається довільним, хоча я б очікував, що це буде працювати. Це визначена поведінка? Чи безпечно це перегляд браузера? Чи є якісь міркування, що лежать в основі того, що це не так, як "специфікація так говорить" (наприклад, це є наслідком якогось більш широкого дизайнерського рішення / філософії)? Приклад зведеного коду:

// inside class definition, itself an object literal, we have this function:
onRender: function() {

    this.menuItems = this.menuItems.concat([
        {
            text: 'Group by Module',
            rptletdiv: this
        },
        {
            text: 'Group by Status',
            rptletdiv: this
        }]);
    // etc
}

це трапляється, коли я теж роблю це var signup = { onLoadHandler:function(){ console.log(this); return Type.createDelegate(this,this._onLoad); }, _onLoad: function (s, a) { console.log("this",this); }};
Deeptechtons


перевірити цю публікацію . Є хороше пояснення різного використання та поведінки цього ключового слова.
Love Hasija

Відповіді:


558

Розстріляний з іншого мого поста, ось вам більше, ніж ви коли-небудь хотіли знати про це .

Перш ніж почати, ось найголовніше, що потрібно пам’ятати про Javascript і повторювати себе, коли це не має сенсу. У Javascript немає класів (ES6 class- синтаксичний цукор ). Якщо щось схоже на клас, це розумна хитрість. У Javascript є об'єкти та функції . (це не на 100% точно, функції - це лише об’єкти, але іноді може бути корисно думати про них як про окремі речі)

Ця змінна прикріплена до функцій. Всякий раз , коли ви викликаєте функцію, це дається певне значення, в залежності від того, як ви викликаєте функції. Це часто називають схемою виклику.

Існує чотири способи викликати функції в JavaScript. Ви можете викликати функцію як метод , як функцію , як конструктор і з застосувати .

Як метод

Метод - це функція, приєднана до об'єкта

var foo = {};
foo.someMethod = function(){
    alert(this);
}

Якщо викликати як метод, це буде пов'язано з об'єктом, до якого входить функція / метод. У цьому прикладі це буде прив’язано до foo.

Як функція

Якщо у вас є окрема функція, ця змінна буде прив’язана до "глобального" об'єкта, майже завжди об'єкта вікна в контексті браузера.

 var foo = function(){
    alert(this);
 }
 foo();

Це може вас змусити , але не відчувайте себе погано. Багато людей вважають це поганим дизайнерським рішенням. Оскільки зворотний виклик викликається як функція, а не як метод, саме тому ви бачите, як видається, непослідовність поведінки.

Багато людей долають проблему, роблячи щось подібне, гм, це

var foo = {};
foo.someMethod = function (){
    var that=this;
    function bar(){
        alert(that);
    }
}

Ви визначаєте змінну , яка вказує на це . Закритість (тема, яка є власною) підтримує це питання , тому якщо ви називаєте панель дзвінків як зворотний дзвінок, вона все ще має посилання.

ПРИМІТКА: У use strictрежимі, якщо він використовується як функція, thisне пов'язаний з глобальним. (Так є undefined).

Як конструктор

Ви також можете викликати функцію як конструктор. Виходячи з правила іменування, яку ви використовуєте (TestObject), це також може бути тим, що ви робите, і те, що вас спонукає .

Ви викликаєте функцію Конструктора за допомогою нового ключового слова.

function Foo(){
    this.confusing = 'hell yeah';
}
var myObject = new Foo();

Коли Ви телефонуєте в якості конструктора, новий об'єкт буде створений, і це буде пов'язано з цим об'єктом. Знову ж таки, якщо у вас є внутрішні функції, і вони використовуються як зворотні дзвінки, ви будете викликати їх як функції, і це буде пов'язано з глобальним об'єктом. Використовуйте той var, що = цей трюк / шаблон.

Деякі люди вважають, що конструктор / нове ключове слово було кісткою, кинутою на Java / традиційних програмістів OOP, як спосіб створити щось подібне до класів.

За допомогою методу нанесення

Нарешті, кожна функція має метод (так, функції - це об'єкти в Javascript) під назвою "застосувати". Застосувати дозволяє визначити , яке значення це буде, а також дозволяє передати масив аргументів. Ось марний приклад.

function foo(a,b){
    alert(a);
    alert(b);
    alert(this);
}
var args = ['ah','be'];
foo.apply('omg',args);

8
Примітка: У строгому режимі , thisбуде undefinedдля функції викликів.
Зловмисник

1
Оголошення функції, наприклад. function myfunction () {}, є окремим випадком "як методу", де "це" - глобальна область (вікно).
Річард

1
@richard: За винятком суворого режиму і thisне має нічого спільного із сферою застосування. Ви маєте на увазі глобальний об’єкт .
TJ Crowder

@ alan-storm. У випадку "Як конструктор" this.confusing = 'hell yeah';те саме, що var confusing = 'hell yeah';? Тож обидва дозволять myObject.confusing? Було б добре, якби не просто так, щоб можна було використовувати thisдля створення властивостей та інших змінних для внутрішньої роботи.
Вт

Але тоді я знову здогадуюсь, що робочий матеріал можна зробити поза функцією, а значення передане конструктору: function Foo(thought){ this.confusing = thought; }а потімvar myObject = new Foo("hell yeah");
wunth

35

Функціональні дзвінки

Функції - це лише тип Об'єкту.

Усі об'єкти Функції мають методи виклику та застосування, які виконують об'єкт Функції, до якого вони викликані.

При виклику перший аргумент цих методів вказує об'єкт, на який буде посилатися thisключове слово під час виконання функції - якщо він використовується, nullабо undefined, глобальний об'єкт, windowвикористовується this.

Таким чином, виклик функції ...

whereAmI = "window";

function foo()
{
    return "this is " + this.whereAmI + " with " + arguments.length + " + arguments";
}

... з дужками - foo()- еквівалентно foo.call(undefined)або foo.apply(undefined), що фактично те саме, що foo.call(window)або foo.apply(window).

>>> foo()
"this is window with 0 arguments"
>>> foo.call()
"this is window with 0 arguments"

Додаткові аргументи callпередаються як аргументи до виклику функції, тоді як єдиний додатковий аргумент, який applyможе вказати аргументи для виклику функції як об’єкт, схожий на масив.

Таким чином, foo(1, 2, 3)еквівалентно foo.call(null, 1, 2, 3)або foo.apply(null, [1, 2, 3]).

>>> foo(1, 2, 3)
"this is window with 3 arguments"
>>> foo.apply(null, [1, 2, 3])
"this is window with 3 arguments"

Якщо функція є властивістю об'єкта ...

var obj =
{
    whereAmI: "obj",
    foo: foo
};

... доступ до посилання на функцію через об'єкт та виклик його в дужках - obj.foo()- еквівалентно foo.call(obj)або foo.apply(obj).

Однак функції, які є властивостями об'єктів, не "прив'язані" до цих об'єктів. Як ви бачите у визначенні objвище, оскільки Функції є лише типом Об'єкта, на них можна посилатися (і, таким чином, можна передавати посиланням на виклик функції або повертати посиланням з виклику функції). Коли посилання на функції передається ніякої додаткової інформації про те, де він був прийнятий з здійснюється з нею, тому наступне не відбувається:

>>> baz = obj.foo;
>>> baz();
"this is window with 0 arguments"

Виклик до нашої функції посилання на функцію bazне містить жодного контексту для виклику, тому він фактично такий самий, як і baz.call(undefined), в thisкінцевому підсумку, посилання window. Якщо ми хочемо bazзнати , що він належить obj, нам потрібно якимось - то чином надати цю інформацію , коли bazназивається, яка , де перший аргумент callабо applyі затвори вступають в гру.

Області застосування

function bind(func, context)
{
    return function()
    {
        func.apply(context, arguments);
    };
}

Коли функція виконується, вона створює нову область і має посилання на будь-яку область, що додається. Коли анонімна функція створена у наведеному вище прикладі, вона посилається на область, в якій вона була створена, яка є bindсферою. Це відомо як "закриття".

[global scope (window)] - whereAmI, foo, obj, baz
    |
    [bind scope] - func, context
        |
        [anonymous scope]

Коли ви намагаєтеся отримати доступ до змінної, цей "ланцюг області" проходить, щоб знайти змінну з вказаним іменем - якщо поточна область не містить змінну, ви переглядаєте наступну область в ланцюжку тощо, поки не досягнете глобальна сфера. Коли анонімна функція повертається і bindзакінчується виконанням, анонімна функція все ще має посилання на область bind's, тому bindобласть дії не «відходить».

З огляду на все вищесказане, тепер ви зможете зрозуміти, як працює область в наступному прикладі, і чому техніка передачі функції навколо "попередньо пов'язаної" з певним значенням thisїї буде мати, коли вона називається працює:

>>> baz = bind(obj.foo, obj);
>>> baz(1, 2);
"this is obj with 2 arguments"

"Коли передається посилання на функцію, ніякої додаткової інформації про те, звідки вона передана, з нею не переноситься", дякую @insin за це.
Алекс Марандон

9

Це визначена поведінка? Чи безпечно це перегляд браузера?

Так. І так.

Чи є якісь міркування, які лежать в основі, чому саме так воно і є ...

Сенс thisвивести досить просто:

  1. Якщо thisвикористовується всередині функції конструктора, а функція викликала newключове слово, thisвідноситься до об'єкта, який буде створений. thisбуде продовжувати означати об’єкт навіть у публічних методах.
  2. Якщо thisвін використовується деінде, включаючи вкладені захищені функції, він посилається на глобальну область застосування (що у випадку браузера є об’єктом вікна).

Другий випадок - це, очевидно, недолік дизайну, але обійти його досить легко, використовуючи закриття.


4

У цьому випадку внутрішня thisпов'язана з глобальним об'єктом замість thisзмінної зовнішньої функції. Це так, як побудована мова.

Дивіться "JavaScript: хороші частини" Дугласа Крокфорда для гарного пояснення.


4

Я знайшов гарний підручник щодо ECMAScript цього

Це значення є спеціальним об'єктом, який пов'язаний з контекстом виконання. Отже, він може бути названий об'єктом контексту (тобто об'єктом, в якому контекст активований контекстом виконання).

Будь-який об'єкт може використовуватися як це значення контексту.

a це значення є властивістю контексту виконання, але не властивістю об'єкта змінної.

Ця особливість дуже важлива, оскільки, на відміну від змінних, це значення ніколи не бере участі в процесі вирішення ідентифікатора. Тобто при доступі до цього в коді його значення приймається безпосередньо з контексту виконання та без будь-якого пошуку ланцюга діапазону. Значення цього визначається лише один раз при входженні в контекст.

У глобальному контексті це значення є самим глобальним об'єктом (це означає, що це значення тут дорівнює змінному об'єкту)

У випадку контексту функції це значення у кожного виклику функції може бути різним

Посилання на Javascript-the core та Chapter 3-this


" У глобальному контексті це значення є самим глобальним об'єктом (це означає, що це значення тут дорівнює об'єкту змінної) ". Глобальний об'єкт є частиною глобального контексту виконання, як це (ES4) «змінюваний об'єкт» і запис середовища ES5. Але вони є різними об'єктами глобального об'єкта (наприклад, запис про оточення не може бути посилається безпосередньо, це заборонено специфікацією, але глобальний об'єкт може бути).
RobG
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.