In depth JavaScript learning notes: this point of function
- Reference: Teacher Wang Hongyuan's in-depth JavaScript course
Why do you need this?
- In common programming languages, almost all have this keyword (self is used in Objective-C), but this in JavaScript is different from this in common object-oriented languages:
- In common object-oriented programming languages, such as Java, C + +, Swift, Dart and so on, this usually only appears in the methods of classes.
- That is, you need to have a class, class method (especially instance method), this represents the current calling object.
- But this in JavaScript is more flexible, no matter where it appears or what it represents.
- Let's take a look at the difference between writing an obj object with this and without this
What does this point to?
- What does this point to under the global action?
// In most cases, this appears in the function // Under global scope // Browser: window(globalObject) // Node environment: {} console.log(this) // console.log(window)
- However, this is rarely used directly under the global action in development, and it is usually used in functions.
- When all functions are called, an execution context will be created:
- In this context, the call stack and AO object of the function are recorded;
- this is also one of the records;
What does this point to?
- Example:
// What this points to has nothing to do with the position of the function // It has something to do with the way the function is called function foo() { console.log(this) } // 1. Call this function directly foo() // 2. Create an object in which the function points to foo var obj = { name: 'why', foo: foo } obj.foo() // 3.apply call foo.apply("abc")
- Let's start with a confusing question:
- Defining a function, we call it in three different ways, and it produces three different results
- What enlightenment can this case give us?
- 1. When the function is called, JavaScript will bind a value to this by default;
- 2. The binding of this has nothing to do with the defined location (the location of writing);
- 3. The binding of this is related to the calling method and the calling location;
- 4.this is bound at runtime;
- So what kind of binding rules is this?
- Binding 1: default binding;
- Binding 2: implicit binding;
- Binding 3: display binding;
- Binding: new
Rule 1: default binding
- When do I use the default binding? Independent function call.
- Independent function call can be understood as that the function is not bound to an object for call;
- Let's take a look at several cases. For the common default binding, the following this is all window
// Default binding: independent function call // 1. Case 1: function foo() { console.log(this) } foo() // 2. Case 2: function foo1() { console.log(this) } function foo2() { console.log(this) foo1() } function foo3() { console.log(this) foo2() } foo3() // 3. Case 3: var obj = { name: "why", foo: function() { console.log(this) } } var bar = obj.foo bar() // window // 4. Case 4: function foo() { console.log(this) } var obj = { name: "why", foo: foo } var bar = obj.foo bar() // window // 5. Case 5: function foo() { function bar() { console.log(this) } return bar }
Rule 2: implicit binding
- Another common calling method is to call through an object:
- That is, in its calling position, it is a function call initiated by an object.
- Let's take a look at the common default bindings through several cases
- Implicit binding: object fn()
- The object object will be bound to this in the fn function by the js engine
function foo() { console.log(this) } // Independent function call // foo() // 1. Case 1: var obj = { name: "why", foo: foo } obj.foo() // obj object // 2. Case 2: var obj = { name: "why", eating: function() { console.log(this.name + "I'm eating") }, running: function() { console.log(obj.name + "Running") } } obj.eating() obj.running() // 3. Case 3: var obj1 = { name: "obj1", foo: function() { console.log(this) } } var obj2 = { name: "obj2", bar: obj1.foo } obj2.bar()
Rule 3: display binding
- Implicit binding has one prerequisite:
- There must be a reference to the function (such as an attribute) inside the called object;
- If there is no such reference, an error that the function cannot be found will be reported when calling;
- It is through this reference that this is indirectly bound to this object;
- What should we do if we don't want to include a reference to this function inside the object and want to make a forced call on this object?
- All JavaScript functions can use call and apply methods (this is related to Prototype).
- The difference between them will not be expanded here;
- In fact, it is very simple. The first parameter is the same. For the following parameters, apply is the array and call is the parameter list;
- The first parameter of these two functions requires an object. What is the function of this object? It's for this.
- When this function is called, this will be bound to the incoming object.
- All JavaScript functions can use call and apply methods (this is related to Prototype).
- Because of the above process, we explicitly bind the object pointed to by this, so it is called display binding
- Example:
function foo() { console.log("The function was called", this) } //1. The difference between foo direct call and call/apply call lies in the difference of this binding foo The direct call points to the global object(window) foo() var obj = { name: "obj" } //call/apply is a binding object that can specify this foo.call(obj) foo.apply(obj) foo.apply("aaaa")
- Output:
- Example:
// 2. What's the difference between call and apply? function sum(num1, num2, num3) { console.log(num1 + num2 + num3, this) } sum.call("call", 20, 30, 40) sum.apply("apply", [20, 30, 40]) //call and apply can explicitly bind this when executing functions. This binding rule is called display binding
- Output:
- What can we do if we want a function to always be bound to an object?
function foo() { console.log(this) } // foo.call("aaa") // foo.call("aaa") // foo.call("aaa") // foo.call("aaa") // Default binding and display binding conflict: priority (display binding) var newFoo = foo.bind("aaa") newFoo() newFoo() newFoo()
new binding
- Functions in JavaScript can be used as constructors of a class, that is, using the new keyword.
- If you use the new keyword to call a function, the following operations will be performed:
- 1. Create a new object;
- 2. The new object will be prototype d;
- 3. The new object will be bound to this of the function call (this binding is completed in this step);
- 4. If the function does not return other objects, the expression will return this new object;
- Example:
// When we call a function (constructor) through a new keyword, this is the object created when calling the constructor // this = created object // This binding process is called new binding function Person(name, age) { this.name = name this.age = age } var p1 = new Person("why", 18) console.log(p1.name, p1.age) var p2 = new Person("kobe", 30) console.log(p2.name, p2.age)
Rule Priority
- 1. The default rule has the lowest priority
- There is no doubt that the default rule has the lowest priority, because when there are other rules, this will be bound by other rules
- 2. The priority of display binding is higher than that of implicit binding
var obj = { name: "obj", foo: function() { console.log(this) } } obj.foo() // 1. The explicit binding of call / apply is higher than the implicit binding obj.foo.apply('abc') obj.foo.call('abc') // 2.bind takes precedence over implicit binding function foo() { console.log(this) } var obj = { name: "obj", foo: foo.bind("aaa") } obj.foo()
- 3.new binding takes precedence over implicit binding
var obj = { name: "obj", foo: function() { console.log(this) } } // new takes precedence over implicit binding var f = new obj.foo() // foo {}
- 4. The binding priority of new is higher than that of bind
- new binding, call and apply are not allowed to be used at the same time, so there is no higher priority
- New binding can be used together with bind, and new binding has higher priority
// new takes precedence over bind function foo() { console.log(this) } var bar = foo.bind("aaa") var obj = new bar() // foo {}
- new binding > apply / call / bind > implicit binding (obj. Foo()) > default binding (independent function call)
Outside this rule – ignore display bindings
- If we pass in null or undefined in the display binding, the display binding will be ignored and the default rule will be used:
Outside this rule - indirect function reference
- In another case, create an indirect reference to a function, which uses the default binding rules.
- The result of assignment (obj2.foo = obj1.foo) is foo function;
- If foo function is called directly, it is the default binding;
Arrow functionarrow function
- Arrow function is a method of writing function added after ES6, and it is more concise than function expression:
- The arrow function will not bind this and arguments attributes;
- The arrow function cannot be used as a constructor (it cannot be used with new, and an error will be thrown);
- How to write arrow function?
- (): parameters of function
- {}: executor of function
Programming optimization of arrow function
- Example:
// 1. Write arrow function // 1> (): parameter // 2> = >: arrow // 3> {}: executor of function var foo = (num1, num2, num3) => { console.log(num1, num2, num3) var result = num1 + num2 + num3 console.log(result) } function bar(num1, num2, num3) { } // When using higher-order functions, you can also pass in arrow functions var nums = [10, 20, 45, 78] nums.forEach((item, index, arr) => {}) // There are some common abbreviations for arrow functions: // Abbreviation 1: if there is only one parameter, () can be omitted nums.forEach(item => { console.log(item) }) // Abbreviation 2: if the function executor has only one line of code, {} can also be omitted // Emphasis: and it will take the execution result of this line of code as the return value by default nums.forEach(item => console.log(item)) var newNums = nums.filter(item => item % 2 === 0) console.log(newNums) // filter/map/reduce var result = nums.filter(item => item % 2 === 0) .map(item => item * 100) .reduce((preValue, item) => preValue + item) console.log(result) // Abbreviation 3: if an arrow function has only one line of code and returns an object, how to write the abbreviation at this time // var bar = () => { // return { name: "why", age: 18 } // } var bar = () => ({ name: "why", age: 18 })
Outside this rule – ES6 arrow function
- The arrow function does not use the four standard rules of this (that is, it does not bind this), but determines this according to the outer scope.
var name = "why" var foo = () => { console.log(this) } foo() var obj = {foo: foo} obj.foo() foo.call("abc")
- Output:
- Take a look at a case that simulates a network request:
- Here, I use setTimeout to simulate the network request. How can I store the requested data in the data?
- We need to get the obj object and set data;
- However, the directly obtained this is window (setTimeout is called directly and bound by default). We need to define VaR in the outer layer_ this = this
- Used in the callback function of setTimeout_ this represents the obj object
- Use the arrow function:
var obj = { data: [], getData: function() { // Send the network request and put the result in the data attribute above // Solution before arrow function // var _this = this // setTimeout(function() { // var result = ["abc", "cba", "nba"] // _this.data = result // }, 2000); // After arrow function setTimeout(() => { var result = ["abc", "cba", "nba"] this.data = result }, 2000); } } obj.getData()
Exercises
- Exercise 1:
var name = "window"; var person = { name: "person", sayName: function () { console.log(this.name); } }; function sayName() { var sss = person.sayName; sss(); // window: independent function call person.sayName(); // person: implicit call (person.sayName)(); // person: implicit call (b = person.sayName)(); // window: assignment expression (independent function call) } sayName();
- Exercise 2:
var name = 'window' var person1 = { name: 'person1', foo1: function () { console.log(this.name) }, foo2: () => console.log(this.name), foo3: function () { return function () { console.log(this.name) } }, foo4: function () { return () => { console.log(this.name) } } } var person2 = { name: 'person2' } person1.foo1(); // Person1 (implicit binding) person1.foo1.call(person2); // Person2 (display binding priority is higher than implicit binding) person1.foo2(); // Window (no scope is bound, and the upper scope is global) person1.foo2.call(person2); // window person1.foo3()(); // Window (independent function call) person1.foo3.call(person2)(); // Window (independent function call) person1.foo3().call(person2); // Person2 (the final call returns the function expression, using the display binding) person1.foo4()(); // Person1 (the arrow function is not bound to this, and the upper scope this is person1) person1.foo4.call(person2)(); // Person2 (a person2 is bound to the displayed upper scope) person1.foo4().call(person2); // Person1 (the upper layer finds person1)
- Exercise 3:
var name = 'window' function Person (name) { this.name = name this.foo1 = function () { console.log(this.name) }, this.foo2 = () => console.log(this.name), this.foo3 = function () { return function () { console.log(this.name) } }, this.foo4 = function () { return () => { console.log(this.name) } } } var person1 = new Person('person1') var person2 = new Person('person2') person1.foo1() // person1 person1.foo1.call(person2) // Person2 (display higher than implicit binding) person1.foo2() // Person1 (this in the upper scope is person1) person1.foo2.call(person2) // Person1 (this in the upper scope is person1) person1.foo3()() // Window (independent function call) person1.foo3.call(person2)() // window person1.foo3().call(person2) // person2 person1.foo4()() // person1 person1.foo4.call(person2)() // person2 person1.foo4().call(person2) // person1 var obj = { name: "obj", foo: function() { } }
- Exercise 4:
var name = 'window' function Person (name) { this.name = name this.obj = { name: 'obj', foo1: function () { return function () { console.log(this.name) } }, foo2: function () { return () => { console.log(this.name) } } } } var person1 = new Person('person1') var person2 = new Person('person2') person1.obj.foo1()() // window person1.obj.foo1.call(person2)() // window person1.obj.foo1().call(person2) // person2 person1.obj.foo2()() // obj person1.obj.foo2.call(person2)() // person2 person1.obj.foo2().call(person2) // obj
Implement apply, call and bind
- Implementation of call function
// Usage of apply/call/bind // js simulate their implementation? difficulty // Add a hycall method to all functions Function.prototype.hycall = function(thisArg, ...args) { // Here you can execute the called function (foo) // Question: you can get which function executes hycall // 1. Get the function to be executed var fn = this // 2. Convert thisArg to object type (prevent it from passing in non object type) thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg): window // 3. Call the function to be executed thisArg.fn = fn var result = thisArg.fn(...args) delete thisArg.fn // 4. Return the final result return result } function foo() { console.log("foo The function is executed", this) } function sum(num1, num2) { console.log("sum The function is executed", this, num1, num2) return num1 + num2 } // call method of system function foo.call(undefined) var result = sum.call({}, 20, 30) // console.log("result of system call:", result) // hycall method of self implemented function // Implicit binding by default // foo.hycall({name: "why"}) foo.hycall(undefined) var result = sum.hycall("abc", 20, 30) console.log("hycall Call of:", result) // var num = {name: "why"} // console.log(Object(num))
- Implementation of apply function
// Implement hyapply by yourself Function.prototype.hyapply = function(thisArg, argArray) { // 1. Get the function to be executed var fn = this // 2. Handle the bound thisArg thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg): window // 3. Execute function thisArg.fn = fn var result // If (! Argarray) {/ / argarray has no value (no parameter passed) // result = thisArg.fn() // }else {/ / parameters passed // result = thisArg.fn(...argArray) // } // argArray = argArray ? argArray: [] argArray = argArray || [] result = thisArg.fn(...argArray) delete thisArg.fn // 4. Return results return result } function sum(num1, num2) { console.log("sum Called", this, num1, num2) return num1 + num2 } function foo(num) { return num } function bar() { console.log("bar The function is executed", this) } // system call // var result = sum.apply("abc", 20) // console.log(result) // Self implemented call // var result = sum.hyapply("abc", [20, 30]) // console.log(result) // var result2 = foo.hyapply("abc", [20]) // console.log(result2) // edge case bar.hyapply(0)
- Implementation of bind function
Function.prototype.hybind = function(thisArg, ...argArray) { // 1. Get the function that needs to be called var fn = this // 2. Bind this thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg): window function proxyFn(...args) { // 3. Put the function into thisArg and call it thisArg.fn = fn // Special: merge two passed in parameters var finalArgs = [...argArray, ...args] var result = thisArg.fn(...finalArgs) delete thisArg.fn // 4. Return results return result } return proxyFn } function foo() { console.log("foo Be executed", this) return 20 } function sum(num1, num2, num3, num4) { console.log(num1, num2, num3, num4) } // bind usage of the system var bar = foo.bind("abc") bar() // var newSum = sum.bind("aaa", 10, 20, 30, 40) // newSum() // var newSum = sum.bind("aaa") // newSum(10, 20, 30, 40) // var newSum = sum.bind("aaa", 10) // newSum(20, 30, 40) // Use your own defined bind // var bar = foo.hybind("abc") // var result = bar() // console.log(result) var newSum = sum.hybind("abc", 10, 20) var result = newSum(30, 40)