preface
Hi, I’m Wakawa. This is the fourth part of the interviewers’ questions series, which aims to help readers improve the basic knowledge of JS, including new, call, apply, this, inheritance related knowledge.
The interviewer asked the following article series: Interested readers can click here to read it.
Q: Can you simulate the JS new operator? Q: can you simulate the JS call and apply methods? Interviewer: JS inheritance
The interviewer gives a lot of examination questions, the basic can change the way to investigate this point, see whether the candidate is solid to JS basic knowledge. You can read the summary at the bottom, then Google (or any other technology platform) a few similar articles to see how the author of the article is different from others (feel free to comment on the differences in the comments section), compare, verify with your existing knowledge if there is a blind spot, read more articles, and naturally improve their own knowledge.
Attached is a paragraph I wrote before: there have been many articles about this, why do I have to write it again. Learning is like a mountain. People climb the mountain along different paths and share the scenery they see. You may not be able to see the scenery others see, feel the mood of others. Only oneself go climbing, can see different scenery, experience is more profound.
The function’s this is bound at the time of the call, depending entirely on where the function is called (that is, where the function is called). To figure out what this refers to, you must know how the related function is called.
Global context
In both non-strict and strict mode this refers to the top-level object (window in the browser).
this === window // true 'use strict' this === window; This.name = 'name '; console.log(this.name); / / if sichuanCopy the code
Function context
Normal function call pattern
Var name = 'window'; var doSth = function(){ console.log(this.name); } doSth(); // 'window'Copy the code
You might mistakenly think that window.dosth () is called, so it points to window. In this case, though, window.dosth does equal doSth. Name equals window.name. In the above code, this is because in ES5, global variables are mounted in the top-level object (the browser is window). In fact, it’s not.
// let name2 = 'window2'; let doSth2 = function(){ console.log(this === window); console.log(this.name2); } doSth2() // true, undefinedCopy the code
In this example, let does not add properties to the top-level object (the browser is window). Window.name2 and window.dosth are both undefined.
In strict mode, this in a normal function behaves differently, as undefined.
// Strict mode 'use strict' var name = 'window'; var doSth = function(){ console.log(typeof this === 'undefined'); console.log(this.name); } doSth(); // true, // error because this is undefinedCopy the code
For those of you who have read the first volume of JavaScript you Don’t Know, you should know that this is called the default binding. Readers familiar with call, apply would use the analogy:
doSth.call(undefined);
doSth.apply(undefined);
Copy the code
The effect is the same. One of the functions that call apply does is to change this to refer to the first argument in the function. The first argument is either undefined or null, or in non-strict mode, refers to window. In strict mode, it refers to the first argument. It will be explained in detail later. This type of code (callback functions) is often used, but it is also a normal function call pattern.
Var name = ' '; setTimeout(function(){ console.log(this.name); }, 0); / / grammar setTimeout (fn | code, 0, arg1, arg2,...). // It can also be a string of code. You can also pass other functions // like setTimeout calling fn internally or executing code 'code'. fn.call(undefined, arg1, arg2, ...) ;Copy the code
The pattern of function (method) calls in an object
var name = 'window'; var doSth = function(){ console.log(this.name); } student = {name: 'other', doSth: doSth, other: {name: 'other', doSth: doSth,}} student.dosth (); // 'student.other.dosth (); DoSth. Call (student); // doSth. Student.other.dosth. Call (student.other);Copy the code
However, there are often scenarios where a function in an object is assigned to a variable. This actually becomes a normal function again, so use the rules of a normal function (default binding).
var studentDoSth = student.doSth; studentDoSth(); StudentDoSth. Call (undefined); studentDoSth. Call (undefined);Copy the code
Call, apply, bind
Invocation pattern
The above mentioned call, apply, here read in detail. Call () : function.prototype.call ()
fun.call(thisArg, arg1, arg2, ...)
Copy the code
ThisArg Specifies the value of this when fun is run. Note that the value specified is not necessarily the actual value of this when the function is executed. If the function is in non-strict mode, null and undefined this values are automatically referred to the global object (which in the browser is the window object) and have the original values (numbers, strings, Boolean) points to an autowrap object for the original value. arg1, arg2, … The return value of the specified argument list is the return value of the method you called, or undefined if the method does not return a value. Apply is similar to call. The parameters are different. Its argument is an array (or array-like).
ThisArg = thisArg = thisArg = thisArg = thisArg In strict mode, thisArg is the original value is the value type, which is the original value. It’s not wrapped as an object. Here’s an example:
var doSth = function(name){ console.log(this); console.log(name); } doSth. Call (2, 'ruokawa '); Var doSth2 = function(name){'use strict'; var doSth2 = function(name){'use strict'; console.log(this); console.log(name); } dosth2. call(2, 'Ruokawa '); // 2, 'Ruokawa'Copy the code
Although it is not common to write thisArg as a value type. But you still need to know this. In the previous article, the interviewer asked whether it is possible to simulate the JS call and apply methods by using the function this on the object to refer to it. Interested readers to think about how to achieve, then see the author of the implementation.
Bind is similar to call and apply. The first argument to bind is changed to this, but the return value is the new function, which can also be called as the constructor (new). MDN Function.prototype.bind
The bind() method creates a new function. When the new function is called, the value provided by this key is the value, and the first few items in the argument list are the sequence of arguments specified at the time of creation.
Syntax: fun.bind(thisArg[, arg1[, arg2[,…]]]) Argument: the value passed to the target function as this argument when thisArg calls the binding function. If the binding function is constructed using the new operator, this value is ignored. When you create a function (provided as a callback) in setTimeout using bind, any original value passed as thisArg is converted to object. If no binding argument is provided, this of the execution scope is treated as thisArg of the new function. arg1, arg2, … When the binding function is called, these arguments are passed to the bound method before the arguments. Return Value Returns a copy of the original function modified with the specified this value and initialization parameter.
The interviewer asked if it is possible to simulate the JS bind method by using call and apply to refer to thisArg parameter. Interested readers to think about how to achieve, then see the author of the implementation.
Constructor call pattern
function Student(name){ this.name = name; console.log(this); // {name: 'wakawa '} // Return this; } var result = new Student(' Student ');Copy the code
Calling a function with the new operator automatically performs the following steps.
- A completely new object is created.
- This object will be executed
[[Prototype]]
(i.e.__proto__
Links).- The generated new object is bound to the function call
this
.- through
new
Each object created will eventually be[[Prototype]]
Link to this functionprototype
On the object.- If the function does not return an object type
Object
(including,Functoin
.Array
.Date
.RegExg
.Error
), thennew
The function call in the expression automatically returns the new object.
We know that when the new operator is called, this refers to the new object that is generated. As a special reminder, the return value of a new call, if there is no explicit return object or function, returns the generated new object.
function Student(name){ this.name = name; // return function f(){}; // return {}; } var result = new Student(' Student '); console.log(result); {name: 'ruokawa '} // If f is returned, result is f; if object {}, result is object {}Copy the code
Many people or articles ignore this and simply use Typeof to judge objects. Although the actual use will not show the return, but the interviewer will ask.
In the previous article, the interviewer asked: can you simulate the JS new operator, using apply to point this to the newly generated object? Interested readers to think about how to achieve, then see the author of the implementation.
Invocation patterns in the prototype chain
function Student(name){ this.name = name; } var s1 = new Student(' s1 '); Student.prototype.doSth = function(){ console.log(this.name); } s1.doSth(); // 'rho'Copy the code
You’ll find this familiar. This is the method call pattern on an object. Pointing to the generated new object, naturally. If the object inherits from another object. Again, it looks through the prototype chain. The above code uses the ES6 class rule:
class Student{ constructor(name){ this.name = name; } doSth(){ console.log(this.name); }} let s1 = new Student(' Student '); s1.doSth();Copy the code
The results of Babel ES6 conversion to ES5, you can go to the Babeljs website conversion test to try yourself.
'use strict'; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; } (); function _classCallCheck(instance, Constructor) { if (! (instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Student = function () { function Student(name) { _classCallCheck(this, Student); this.name = name; } _createClass(Student, [{ key: 'doSth', value: function doSth() { console.log(this.name); } }]); return Student; } (); Var s1 = new Student(' s1 '); s1.doSth();Copy the code
As you can see, classes in ES6 are also implemented through constructor emulation, a kind of syntactic sugar.
Arrow function call pattern
Let’s start with an important difference between arrow functions and normal functions:
No this, super, arguments, and new.target bindings.
2, cannot be called using new. 3. No prototype objects. 4. You cannot change this binding. Parameter names must be unique.
There is no this binding in the arrow function and its value must be determined by looking up the scope chain. If the arrow function is contained by a non-arrow function, this is bound to this of the nearest non-arrow function, otherwise the value of this is set to a global object. Such as:
var name = 'window'; Function (){// var self = this; var arrowDoSth = () => { // console.log(self.name); console.log(this.name); } arrowDoSth(); }, arrowDoSth2: () => { console.log(this.name); } } student.doSth(); // 'ruokawa' student.arrowdosth2 (); // 'window'Copy the code
So this is the equivalent of the this outside of the arrow function that is cached to the normal function above the arrow function. If there is no normal function, it is a global object (or window in the browser). You cannot bind this to the arrow function (it does not have this) via call, apply, or bind. Call, apply, and bind can bind this to the normal function above the cache arrow function. Such as:
Function (){console.log(this.name); var student = {name: 'ruoku ', doSth: function(){console.log(this.name); return () => { console.log('arrowFn:', this.name); } } } var person = { name: 'person', } student.doSth().call(person); // 'wakawa' 'arrowFn:' 'Wakawa' student.dosth. Call (person)(); // 'person' 'arrowFn:' 'person'Copy the code
DOM
Event handler function calls
AddEventerListener, attachEvent, and onClick
<button class="button">onclick</button>
<ul class="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
var button = document.querySelector('button');
button.onclick = function(ev){
console.log(this);
console.log(this === ev.currentTarget); // true
}
var list = document.querySelector('.list');
list.addEventListener('click', function(ev){
console.log(this === list); // true
console.log(this === ev.currentTarget); // true
console.log(this);
console.log(ev.target);
}, false);
</script>
Copy the code
Onclick and addEventerListener are elements that point to the binding event. Some browsers, such as IE6 to IE8, use attachEvent, where this refers to window. By the way: interviewers often look at the difference between ev.currenttarget and ev.target. Ev.currenttarget is the element that binds the event, and ev.target is the element that currently fires the event. For example, here we have UL and li. But it could also be ul, in which case ev.currenttarget and ev.target are equal.
Inline event handler function calls
<button class="btn1" onclick="console.log(this === document.querySelector('.btn1'))"> </button> <button class="btn1" onclick="console.log(this === document.queryselector ('.btn1'))" onclick="console.log((function(){return this})());" </button>Copy the code
The first is button itself, so true, and the second is window. This has nothing to do with strict patterns. Now, of course, we don’t use it that way anymore, but sometimes we accidentally write it that way, and that’s something to understand.
There are many other ways to use this, such as this, new Function(), eval for getters and setters of objects. But grasp the above and analyze the rest, and it will take care of itself. More commonly used or ordinary function calls, object function calls, new calls, calls, apply, bind calls, arrow function calls. So what are their priorities?
priority
The arrow function’s this is either the upper level normal function’s this or the global object (window in the browser), so it is excluded and does not have priority.
var name = 'window'; var person = { name: 'person', } var doSth = function(){ console.log(this.name); return function(){ console.log('return:', this.name); }} var Student = {name: 'doSth ', doSth: doSth,} // window.dosth (); // window.dosth (); Student.dosth. Call (person); // 'person' new Student.doSth.call(person);Copy the code
Imagine if student.dosth. Call (person) was executed first, then new executes a function. There is no problem. In fact, the code is in error. Function. Prototype. call (); Function. Prototype. call (); So the error is reported.
Uncaught TypeError: Student.doSth.call is not a constructor
Copy the code
This is because there are two different methods inside the function: [[Call]] and [[Constructor]]. When using a normal function Call, [[Call]] is executed. When a Constructor call is used, [[Constructor]] is executed. There is no [[Constructor]] method inside the call, apply, bind, and arrow functions.
As you can see from the above example, ordinary function calls have the lowest priority, followed by functions on objects. The priority of call (apply, bind) and new calls is compared to new, which refers to the ployfill implementation of MDN’s bind. When new is called, the first argument to bind is ignored. For those of you who are interested in the MDN implementation, see my previous article: Interviewer asks: Can you simulate javascript’s bind method? So their priority is new calls > Call, apply, bind calls > function calls on the object > normal function calls.
conclusion
To determine the this binding of a running function, you need to find where the function was called directly. Once found, the following four rules can be applied in order to determine the binding object for this.
new
Call: bind to the newly created object, note: displayreturn
A function or object whose return value is not a newly created object, but an explicitly returned function or object.call
orapply
(orbind
) call: In strict mode, bind to the first argument specified. In the non-strict mode,null
andundefined
, pointing to the global object (yes in the browserwindow
), and the remaining values point tonew Object()
The wrapped object.- Function calls on an object: bind to that object.
- Normal function call: Bound to in strict mode
undefined
Otherwise it is bound to a global object.
Arrow function in ES6: Instead of using the four standard binding rules above, this is determined based on the current lexical scope. Specifically, the arrow function inherits the outer function, calls the this binding (whatever this is bound to), and without the outer function, is bound to the global object (window in the browser). This is actually the same as the self = this mechanism in the previous ES6 code.
DOM event functions: Usually refer to a DOM element that is bound to an event, but in some cases bind to a global object (such as attachEvent in IE6 to IE8).
Be aware that some calls may inadvertently use normal function binding rules. If you want to “safely” ignore this binding, you can use an Object such as ø = object.create (null) to protect the global Object.
New, call, apply, bind, arrow functions, etc. This extends to scopes, closures, prototype chains, inheritance, strict patterns, and so on. That’s why interviewers love it.
Readers are welcome to point out any shortcomings or improvements they find. In addition, I think it is well written, and I can point a like, which is also a kind of support for the author.
Examination questions
This points to questions that are often examined in combination with operators, etc. After reading this article, try the following two interview questions. A small sea: a often despised front-end JS interview questions from these two sets of questions, re-understand JS this, scope, closure, object
Further reading
You do not know JavaScript roll Yuba: JavaScript in-depth interpretation of this from ECMAScript specification this wave can kill: front-end basic advanced (five) : comprehensive interpretation of this
Selected articles by the author
Learn how to use sentry source code and build their own front-end exception monitoring SDK. Learn how to use lodash source code and build their own functional programming library. Q: Can you simulate the js call and apply methods? Q: Can you simulate the js bind method? Q: Can you simulate the js bind method? Can simulate the implementation of the JS new operator front end using puppeteer crawler to generate the React. JS small book PDF and merge
about
Author: often with the name of Ruochuan mixed in rivers and lakes. The front road lovers | | PPT know little, only good study. Personal blog segmentfault front view column, opened front view column, welcome to pay attention to ~ gold column, welcome to pay attention to ~ github front view column, welcome to pay attention to ~ github blog, find a star^_^~
Wechat public account Ruochuan Vision
May be more interesting wechat public number, long press scan code concern. You can also add wechat Ruochuan12, indicate the source, pull you into [front view communication group].