Home  >  Article  >  Web Front-end  >  Summary of programming skills that js must know

Summary of programming skills that js must know

伊谢尔伦
伊谢尔伦Original
2017-06-16 10:38:251547browse
#Are you new to js programming? If so, you may be frustrated. All languages ​​have their quirks - but developers moving from a server-side language based on strong typing can be confused. I was like that, a few years ago, when I was thrust into becoming a full-time JavaScript developer, and there were a lot of things I wish I'd known at the beginning. In this article, I'm going to share some of my quirks and hopefully I can share with you some of the experiences that have given me a lot of headaches. This isn't a complete list - just a partial list - but hopefully it opens your eyes to the power of this language, and things you may have once considered a hindrance.


We will look at the following techniques:

  • Equal

  • Dot vs parentheses

  • ##Function context

  • Function declaration vs function expression

  • Named vs anonymous function

  • Immediately execute function expression

  • typeof vs Object.prototype.toString


1.) Equality
Coming from C#, I am very familiar with the == comparison operator. Value types (or strings) are equal when they have the same value. Reference type equality requires having the same reference. (We assume you don't overload the == operator, or implement your own equality operator and GetHashCode method) I'm surprised why JavaScript has two equality operators: == and ===. Initially most of my code was using ==, so I had no idea what JavaScript was doing for me when I ran:

  1. var x = 1;


  2. ##if(x == "1") {


  3. ## console.log("YAY! They're equal!");

  4. }
Is this dark magic? How is the integer 1 equal to the string "1"?

In JavaScript, there are equality (==) and strict equality (===). The equality operator will perform a strict equality comparison after coercing both operands to the same type. So in the above example, the string "1" will be converted to the integer 1, this process happens behind the scenes, and then compared with the variable x.

Strict equality does not perform type conversion. If the operand types are different (such as integer and string), then they are not congruent (strictly equal).


##var x = 1;


  1. ##// Strict equality, the types must be the same


  2. if(x === "1") {


  3. ## console.log("Sadly, I'll never write this to the console");


  4. }


  5. if(x === 1) {

  6. ## console.log("YES! Strict Equality FTW.")

  7. }


You may be thinking about all kinds of horror problems caused by possible casts - assuming that such a conversion occurs in your reference, it may make it very difficult for you to find where the problem lies. This is not surprising and is why experienced JavaScript developers always recommend using strict equality.

2.) Dot vs bracket
Depending on what other language you come from, you may or may not have seen this way (this It’s just nonsense).

  1. //Get the firstName value of the person object

  2. var name = person.firstName;


  3. ##// Get the array The third element


  4. ##var theOneWeWant = myArray[2]; // remember, 0-based index don’t forget the first element The index is 0
  5. However, did you know it can also reference members of an object using parentheses? For example:


##var name = person["firstName"];
  1. ##Why is this useful? While you'll be using dot notation most of the time, there are a few instances of parentheses making it possible for certain methods to fail to do so. For example, I will often refactor large switch statements into a schedule, so something like this:


Why can it be used like this? You may be more familiar with using dots before, but there are a few special cases where only bracket notation is available. For example, I will often refactor the switch statement into a lookup table (which is faster), and actually look like this:


var doSomething = function(doWhat) {


  1. switch(doWhat) {


  2.            case "doThisThing":


  3. ##                                                                                                                                                                                                 

    # break;

  4. ## case "doThatThing":


  5. // More code ...


  6. # Break

  7. #                                                                                                                                                                                                                                         

  8. break;

  9. ## // additional cases here, etc.

  10. # Default:

  11. // Default Behavior
    ##

  12. break;

  13. ## }

  14. }


can be converted into something like the following:

  1. var thingsWeCanDo = {

  2. doThisThing : function() { /* behavior */ },

  3. ## doThatThing : function() { /* behavior */ },


  4. ## doThisOtherThing : function() { /* behavior */ },

  5. ## default : function() { /* behavior */ }

  6. };


  7. ##var doSomething = function(doWhat) {


  8. ## var thingToDo = thingsWeCanDo.hasOwnProperty(doWhat) ? doWhat : "default"


  9. thingsWeCanDo[thingToDo]();

  10. ##}

  11. There is nothing wrong with using switch (and in many cases, if being iterated many times and performance is a big concern, switch may be better than a lookup table perform better). However lookup tables provide a great way to organize and extend your code, and parentheses allow your properties to be evaluated lazily.

3.) Function Context
There have been some great bloggers who have published articles on the proper understanding of this context in JavaScript (at the end of the article I Will give some nice links), but it really should be added to the "I wish I knew" list. It's really difficult to understand the code and confidently know the value of this at any position - you just need to learn a set of rules. Unfortunately, many of the explanations I read early on only added to my confusion. So I tried to explain it succinctly.

First - first consider the global situation (Global)
By default, until the execution context is changed for some reason, the value of this is Points to the global object. In a browser, that would be the window object (or global in node.js).

Second - the this value in the method
When you have an object with a function member, call this method on the parent object, this The value of will point to the parent object. For example:


##var marty = {

  1. # # firstName: "Marty",


  2. lastName: "McFly",


  3. timeTravel: function(year) {


  4. ## console.log(this.firstName + " " + this.lastName + " is time traveling to " + year);
  5. }

  6. ##}


  7. ##marty.timeTravel(1955);


  8. ##/ / Marty McFly is time traveling to 1955
#You may already know that you can reference the timeTravel method of the marty object and create a new reference to another object. This is actually a very powerful feature of JavaScript - allowing us to reference behavior (calling functions) on different instances.



    var doc = {

  1. firstName: "Emmett ",

  2. lastName: "Brown",

  3. }


  4. #doc.timeTravel = marty.timeTravel;
  5. So - what will happen if we call doc.timeTravel(1885)?



##doc.timeTravel(1885);


  1. / / Emmett Brown is time traveling to 1885

  2. ##Once again - performing dark magic. Well, not really. Remember, when you call a method, this context is the parent object of the called function's parent.


What happens when we save a reference to the marty.TimeTravel method and then call our saved reference? Let's see:


var getBackInTime = marty.timeTravel;


  1. getBackInTime(2014);

  2. ##// undefined undefined is time traveling to 2014

  3. Why is it "undefined undefined"? ! Instead of "Matry McFly"?


Let's ask the key question: What is the parent/container object when we call our getBackInTime function? When the getBackIntTime function exists in the window, we call it as a function, not as an object method. When we call a function like this - without a container object - this context will be the global object. David Shariff has a great description of this: Whenever a function is called, we must immediately look to the left of the parenthesis. If there is a reference on the left side of the bracket, then the this value passed to the calling function is determined to be the object to which the reference belongs, otherwise it is a complete object.
Since the this context of getBackInTime is window - there are no firstName and lastName properties - this explains why we see "undefined undefined".

So we know that calling a function directly - without a container object - the result of this context is the global object. However, I also said that I already knew that our getBackInTime function existed on the window. How do I know? Okay, unlike above where I wrapped getBackInTime in a different context (we were talking about executing function expressions immediately), any variables I declared were added to the window. Verification from Chrome console:




##It’s time to discuss the main point of this One of the areas where it comes in handy: subscription event processing.

Third (just an extension of #2) - the this value in the asynchronous call method
So, let's pretend we want to call our marty.timeTravel method when someone clicks a button:


##var flux = document.getElementById("flux-capacitor");

  1. ##flux.addEventListener("click", marty.timeTravel);


  2. ##In the above code, when the user clicks the button, we will see "undefined undefined is time traveling to [object MouseEvent]". What? OK - first of all, the very obvious problem is that we didn't provide the year parameter to our timeTravel method. Instead, we directly subscribe to this method as an event handler, and the MouseEvent parameter is passed as the first argument to the event handler. This is easy to fix, but the real problem is that we see "undefined undefined" again. Don’t be hopeless—you already know why this happens (even if you don’t realize it yet). Let's modify our timeTravel function to output this to help us figure out the facts:
  3. ##marty.timeTravel = function(year) {



console.log(this.firstName + " " + this.lastName + " is time traveling to " + year);

  1. console.log(this);

  2. ##};


Now - when we click this button, we will have output similar to the following in your browser console:


When the method is called, the second console.log outputs this context - which is actually the button element to which we subscribed to the event. Are you surprised? Just like before - when we assigned marty.timeTravel to the getBackInTime variable - the reference to marty.timeTravel is saved to the event handler and called, but the container object is no longer a marty object. In this case it will be called asynchronously on the click event of the button instance.

So - is it possible to set this to the result we want? Absolutely! In this case, the solution is very simple. Instead of subscribing to marty.timeTravel directly in the event handler, use an anonymous function as the event handler and call marty.timeTravel in the anonymous function. This can also fix the problem of missing year parameter.

  1. #flux.addEventListener("click", function(e) {

  2. marty.timeTravel(someYearValue);

  3. ##});

Clicking the button will output information similar to the following to the console:



Success! But why is this okay? Think about how we call the timeTravel method. In our first example of a button click, we subscribed to a reference to the method itself in the event handler, so it was not called from the parent marty. In the second example, this is the anonymous function of the button element, and when we call marty.timeTravel, we call it from its parent object marty, so this is marty.
Fourth - this value in the constructor
When you use the constructor to create an object instance, the this value inside the function is newly created object. For example:

    ##var TimeTraveler = function(fName, lName) {

  1. this.firstName = fName;

  2. ## this.lastName = lName;

  3. // Constructor functions return the

  4. ## // newly created object for us unless


  5. // we specifically return something else


  6. ##};


  7. ##var marty = new TimeTraveler("Marty", "McFly");


  8. console.log(marty.firstName + " " + marty.lastName);

  9. // Marty McFly


Call, Apply and BindCall
You may start to wonder, in the above example, there is no language level feature that allows us to Is the this value of the calling function specified at runtime? you are right. The call and apply methods present on the function prototype allow us to call the function and pass the this value.

The first parameter of the call method is this, followed by the parameter sequence of the called function:

  1. someFn.call(this, arg1, arg2, arg3);


The first parameter of apply is also this, followed by An array composed of the remaining parameters:

  1. ##someFn.apply(this, [arg1, arg2, arg3]);


Our instances of doc and marty can time travel on their own, but einstein (Einstein) needs their help to complete time travel. So let's add a method to our doc instance so that doc can help einstein with its time travel.

  1. ##doc.timeTravelFor = function(instance, year) {


  2. this.timeTravel.call(instance, year);


  3. ## // If you use apply use the following syntax

  4. // this.timeTravel.apply(instance, [year]);

  5. };
Now it can teleport Einstein:



    var einstein = {

  1. firstName: "Einstein",

  2. lastName: "(the dog)"

  3. ##};

  4. doc.timeTravelFor(einstein, 1985);

  5. // Einstein (the dog) is time traveling to 1985
#I know this example is a bit far-fetched, but it is enough to show you the power of applying functions to other objects.


There is another use of this method that we have not discovered. Let's add a goHome method to our marty instance as a shortcut to this.timeTravel(1985).


##marty.goHome = function() {


  1. this.timeTravel(1985);


  2. ##}


However, we know that if we subscribe to marty.goHome as the button's click event handler, the value of this will be the button - and unfortunately buttons don't have a timeTravel method. We can solve it with the above method - use an anonymous function as the event handler and call the above method inside it - but we have another option - the bind function:

  1. flux.addEventListener("click", marty.goHome.bind(marty));


The bind function will actually return a new function, and the this value of the new function is set according to the parameters you provide. If you need to support lower version browsers (for example: versions below ie9), you may need the shim of the bind function (or, if you use jQuery, you can use $.proxy instead, both underscore and lodash provide_ .bind method).

It is important to remember that if you use the bind method directly on the prototype, it will create an instance method, which will bypass the advantages of the prototype method. This is not a mistake, just make it clear in your mind. I wrote more information about this issue here.

4.) Function expression vs function declaration
Function declaration does not require the var keyword. In fact, as Angus Croll said: "It helps to think of them as siblings of variable declarations". For example:

  1. ##function timeTravel(year) {

  2. ## console.log(this.firstName + " " + this.lastName + " is time traveling to " + year);


  3. } The function name timeTravel in the example above is not only visible in the scope in which it is declared, but also within the function itself (this is very useful for recursive function calls). Function declaration is essentially a named function. In other words, the name attribute of the above function is timeTravel.

A function expression defines a function and assigns it to a variable. Typical applications are as follows:

var someFn = function() {
    console.log("I like to express myself...");
}; 也可以对函数表达式命名——然而,不像函数声明,命名函数表达式的名字仅在它自身函数体内可访问:
var someFn = function iHazName() {
    console.log("I like to express myself...");
    if(needsMoreExpressing) {
        iHazName(); // 函数的名字在这里可以访问
    }
};
// 你可以在这里调用someFn(),但不能调用iHazName()
someFn();

##We cannot discuss function expressions and function declarations without mentioning "hoisting" (hoisted)" - function and variable declarations are moved to the top of the scope by the compiler. We can't explain hoisting in detail here, but you can read two great explanations by Ben Cherry and Angus Croll.
5.) Named vs anonymous function

Based on our discussion just now, you may have guessed that an "anonymous" function is actually an unnamed function. function. Most JavaScript developers will quickly recognize that the first parameter of the year is an anonymous function:

  1. someElement.addEventListener("click", function(e) {

  2. ## // I' m anonymous!

  3. });


However, Similarly, our marty.timeTravvel method is also an anonymous function:

  1. ##var marty = {


  2. firstName: "Marty",


  3. ## lastName: "McFly",

  4. timeTravel: function(year) {

  5. ## console.log(this.firstName + " " + this.lastName + " is time traveling to " + year);

  6. # }


  7. }

  8. Because function declarations must have a unique name, only function expressions can be without name.


6.) Immediate execution of function expressions
Since we are talking about function expressions, there is one thing I wish I had known: immediate execution of function expressions Formula (IIFE). There are many good articles about IIFE (I will list them at the end of the article), but to describe it in one sentence, function expressions are not executed by assigning the function expression to a scalar and executing it later, but by understanding the execution. You can watch this process in the browser console.

First - let's type in a function expression - but don't assign a variable to it - and see what happens:

Syntax error - this is considered a function declaration and is missing a function name. However, to make it an expression, we simply wrap it in parentheses:




After turning it into an expression, the console returns us an anonymous function (remember, we don't assign a value to it, but the expression does return a value). So - we know that "function expression" is part of "immediately calling function expression". For the wait-to-execute feature, we call the returned expression (just like we call other functions) by adding another parenthesis after the expression:




"But wait, Jim! (referring to the author) I think I've seen this call before." In fact you may have seen it - this is the legal syntax (and notoriously Douglas Crockford's preferred syntax)





Both methods will work, but I highly recommend you read here.

OK, great – now we know what IIFE is – and why we should use it?

It helps us control scope - a very important part of any JavaScript tutorial! Many of the examples we saw earlier were created in the global scope. This means that the window (assuming the environment is a browser) object will have a lot of properties. If we all wrote our JavaScript code this way, we would quickly accumulate a ton (exaggeration) of variable declarations in the global scope, and the window code would be polluted. Even under the best of circumstances, exposing a lot of details in a global variable is bad advice, but what happens when the name of the variable is the same as the name of an existing window property? The window property will be overridden!

For example, if your favorite "Amelia Earhart" website declares a navigator variable in the global scope, here is the result before and after setting it:

oops!

Obviously - polluted global variables are bad. JavaScript uses function scope (not block scope, which is very important if you're coming from C# or Java!), so the way to keep our code separated from the global scope is to create a new scope, which we can do using IIFE implementation because its contents are within its own function scope. In the example below, I'll show you the value of window.navigator in the console, and then I'll create an IIFE (immediately executed function expression) to wrap Amelia Earhart's behavior and data. After the IIFE is completed, an object is returned as our "program namespace". The navigator variable I declare inside the IIFE will not override the value of window.navigator.


As an added bonus, the IIFE we created above is the module pattern in JavaScript of enlightenment. I'll include links at the end to some of the module patterns I explored.

7.) 'typeof' operator and 'Object.prototype.toString'
Eventually, you may find that in some cases you need to check The type of argument passed to the function, or something similar. The typeof operator would be the obvious choice, however, it is not a panacea. For example, what happens when we call the typeof operator on an object, array, string, or regular expression?


Fortunately - at least we can distinguish strings from objects, arrays, and regular expressions, right? Fortunately, we can get more accurate type information through different methods. We will use the Object.prototype.toString method and apply the call method we mentioned earlier:


Why do we use the toString method on Object.prototype? Because third-party libraries or your own code may override the instance's toString method. Through Object.prototype, we can force the original toString behavior of the instance.

If you know what typeof will return so you don't need to perform extra checks (for example, you just need to know whether it is a string or not), using typeof is very good. However, if you need to distinguish between arrays and objects, regular expressions and objects, etc., then use Object.prototype.toString.


The above is the detailed content of Summary of programming skills that js must know. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn