Up & Going -2,3 (Into JavaScript && Into YDKJS)

本系列的第一本书,总结完结。下周开始本系列第二本...

Values & Types

JavaScript has typed values, not typed variables. The following built-in types are available:

  • string
  • number
  • boolean
  • null and undefined
  • object
  • symbol (new to ES6)
    The return value from thetypeof operator is always one of six (seven as of ES6! - the "symbol" type) string values. That is, typeof "abc" returns "string", not string.
a = null;
typeof a;               // "object" -- weird, bug

typeof null is an interesting case, because it errantly returns "object", when you'd expect it to return "null".

Warning: This is a long-standing bug in JS, but one that is likely never going to be fixed. Too much code on the Web relies on the bug and thus fixing it would cause a lot more bugs!

Objects

Theobjecttype refers to a compound value where you can set properties (named locations) that each hold their own values of any type.

var obj = {
    a: "hello world",
    b: 42,
    c: true
};
// dot notation
obj.a;      // "hello world"
obj.b;      // 42
obj.c;      // true
// bracket notation
obj["a"];   // "hello world"
obj["b"];   // 42
obj["c"];   // true

Bracket notation is useful if you have a property name that has special characters in it, like obj["hello world!"]-- such properties are often referred to as keys when accessed via bracket notation. The [ ]notation requires either a variable (explained next) or a string literal (which needs to be wrapped in" .. "or' .. ').

var obj = {
    a: "hello world",
    b: 42
};

var b = "a";

obj[b];         // "hello world"
obj["b"];       // 42

Arrays

An array is an objectthat holds values (of any type) not particularly in named properties/keys, but rather in numerically indexed positions.

The best and most natural approach is to use arrays for numerically positioned values and use objects for named properties.

Functions

functions are a subtype of objects -- typeof returns "function", which implies that a function is a main type -- and can thus have properties, but you typically will only use function object properties (like foo.bar) in limited cases.

function foo() {
    return 42;
}

foo.bar = "hello world";

typeof foo;         // "function"
typeof foo();       // "number"
typeof foo.bar;     // "string"

Built-In Type Methods

var a = "hello world";
var b = 3.14159;

a.length;               // 11
a.toUpperCase();        // "HELLO WORLD"
b.toFixed(4);           // "3.1416"

The "how" behind being able to calla.toUpperCase()is more complicated than just that method existing on the value.

Briefly, there is a String(capitalS) object wrapper form, typically called a "native," that pairs with the primitive string type; it's this object wrapper that defines the toUpperCase() method on its prototype.

When you use a primitive value like"hello world" as anobject by referencing a property or method (e.g., a.toUpperCase()in the previous snippet), JS automatically "boxes" the value to its object wrapper counterpart (hidden under the covers).

Comparing Values

There are two main types of value comparison that you will need to make in your JS programs: equality and inequality. The result of any comparison is a strictly boolean value (true or false), regardless of what value types are compared.

Coercion

Coercion comes in two forms in JavaScript: explicit and implicit.

Truthy & Falsy

The specific list of "falsy" values in JavaScript is as follows:

  • "" (empty string)
  • 0,-0, NaN (invalid number)
  • null, undefined
  • false

Any value that's not on this "falsy" list is "truthy."

Equality

There are four equality operators: ==, ===, !=, and !==.
The proper way to characterize them is that ==checks for value equality with coercion allowed, and===checks for value equality without allowing coercion; === is often called "strict equality" for this reason.
there's two possible waysa == bcould give true via coercion. Either the comparison could end up as 42 == 42 or it could be "42" == "42". So which is it?
The answer: "42" becomes42, to make the comparison 42 == 42.
You should take special note of the==and ===comparison rules if you're comparing two non-primitive values, like objects (including function and array). Because those values are actually held by reference, both ==and === comparisons will simply check whether the references match, not anything about the underlying values.

For example, arrays are by default coerced to strings by simply joining all the values with commas (,) in between. You might think that two arrays with the same contents would be == equal, but they're not:

var a = [1,2,3];
var b = [1,2,3];
var c = "1,2,3";

a == c;     // true
b == c;     // true
a == b;     // false
Inequality

The <, >, <=, and >= operators are used for inequality, referred to in the specification as "relational comparison."

Notably, there are no "strict inequality" operators that would disallow coercion the same way ==="strict equality" does.
In section 11.8.5 of the ES5 specification, it says that if both values in the < comparison are strings, as it is with b < c, the comparison is made lexicographically (aka alphabetically like a dictionary). But if one or both is not a string, as it is with a < b, then both values are coerced to be numbers, and a typical numeric comparison occurs.

var a = 42;
var b = "foo";

a < b;      // false
a > b;      // false
a == b;     // false

Wait, how can all three of those comparisons be false? Because the b value is being coerced to the "invalid number value" NaNin the <and > comparisons, and the specification says that NaNis neither greater-than nor less-than any other value.

Variables

In JavaScript, variable names (including function names) must be valid identifiers.

An identifier must start with a-z, A-Z, $, or_. It can then contain any of those characters plus the numerals0-9.
However, certain words cannot be used as variables, but are OK as property names. These words are called "reserved words," and include the JS keywords (for, in, if, etc.) as well as null, true, and false.

Function Scopes

Hoisting

Metaphorically, this behavior is called hoisting, when a var declaration is conceptually "moved" to the top of its enclosing scope.

var a = 2;

foo();                  // works because `foo()`
                        // declaration is "hoisted"

function foo() {
    a = 3;

    console.log( a );   // 3

    var a;              // declaration is "hoisted"
                        // to the top of `foo()`
}

console.log( a );   // 2
Nested Scopes

When you declare a variable, it is available anywhere in that scope, as well as any lower/inner scopes.
bad one:

function foo() {
    a = 1;  // `a` not formally declared
}

foo();
a;          // 1 -- oops, auto global variable :(

This is a very bad practice. Don't do it! Always formally declare your variables.

In addition to creating declarations for variables at the function level, ES6 lets you declare variables to belong to individual blocks (pairs of { .. }), using the let keyword.

function foo() {
    var a = 1;

    if (a >= 1) {
        let b = 2;

        while (b < 5) {
            let c = b * 2;
            b++;

            console.log( a + c );
        }
    }
}

foo();
// 5 7 9

Conditionals

Sometimes you may find yourself writing a series of if..else..ifstatements like this:

if (a == 2) {
    // do something
}
else if (a == 10) {
    // do another thing
}
else if (a == 42) {
    // do yet another thing
}
else {
    // fallback to here
}

This structure works, but it's a little verbose because you need to specify the atest for each case. Here's another option, the switchstatement:

switch (a) {
    case 2:
    case 10:
        // some cool stuff
        //Here, if a is either 2 or 10, it will execute the "some cool stuff" code statements.
        break;
    case 42:
        // other stuff
        break;
    default:
        // fallback
}

The break is important if you want only the statement(s) in one case to run. If you omit break from a case, and that case matches or runs, execution will continue with the next case's statements regardless of that case matching. This so called "fall through" is sometimes useful/desired.

Another form of conditional in JavaScript is the "conditional operator," often called the "ternary operator." It's like a more concise form of a single if..elsestatement, such as:

var a = 42;

var b = (a > 41) ? "hello" : "world";

// similar to:

// if (a > 41) {
//    b = "hello";
// }
// else {
//    b = "world";
// }

Strict Mode

ES5 added a "strict mode" to the language, which tightens the rules for certain behaviors.

Not only will strict mode keep your code to a safer path, and not only will it make your code more optimizable, but it also represents the future direction of the language. It'd be easier on you to get used to strict mode now than to keep putting it off -- it'll only get harder to convert later!

Functions As Values

Not only can you pass a value (argument) to a function, but a function itself can be a value that's assigned to variables, or passed to or returned from other functions.

Immediately Invoked Function Expressions (IIFEs)

There's another way to execute a function expression, which is typically referred to as an immediately invoked function expression (IIFE):

(function IIFE(){
    console.log( "Hello!" );
})();
// "Hello!"

Because an IIFE is just a function, and functions create variable scope, using an IIFE in this fashion is often used to declare variables that won't affect the surrounding code outside the IIFE:

var a = 42;

(function IIFE(){
    var a = 10;
    console.log( a );   // 10
})();

console.log( a );       // 42

Closure

You can think of closure as a way to "remember" and continue to access a function's scope (its variables) even once the function has finished running.

function makeAdder(x) {
    // parameter `x` is an inner variable

    // inner function `add()` uses `x`, so
    // it has a "closure" over it
    function add(y) {
        return y + x;
    };

    return add;
}
// `plusOne` gets a reference to the inner `add(..)`
// function with closure over the `x` parameter of
// the outer `makeAdder(..)`
var plusOne = makeAdder( 1 );

// `plusTen` gets a reference to the inner `add(..)`
// function with closure over the `x` parameter of
// the outer `makeAdder(..)`
var plusTen = makeAdder( 10 );

plusOne( 3 );       // 4  <-- 1 + 3
plusOne( 41 );      // 42 <-- 1 + 41

plusTen( 13 );      // 23 <-- 10 + 13

More on how this code works:

  1. When we call makeAdder(1), we get back a reference to its inner add(..)that remembers xas 1. We call this function reference plusOne(..).
  2. When we call makeAdder(10), we get back another reference to its inner add(..) that remembers xas 10. We call this function reference plusTen(..).
  3. When we call plusOne(3), it adds 3 (its inner y) to the 1 (remembered by x), and we get 4 as the result.
  4. When we call plusTen(13), it adds 13(its innery) to the 10 (remembered by x), and we get 23as the result.

Modules

The most common usage of closure in JavaScript is the module pattern. Modules let you define private implementation details (variables, functions) that are hidden from the outside world, as well as a public API that is accessible from the outside.

function User(){
    var username, password;

    function doLogin(user,pw) {
        username = user;
        password = pw;

        // do the rest of the login work
    }

    var publicAPI = {
        login: doLogin
    };

    return publicAPI;
}

// create a `User` module instance
var fred = User();

fred.login( "fred", "12Battery34!" );

Warning: We are not calling new User()here, on purpose, despite the fact that probably seems more common to most readers. User() is just a function, not a class to be instantiated, so it's just called normally. Using new would be inappropriate and actually waste resources.

Executing User() creates an instance of the Usermodule -- a whole new scope is created, and thus a whole new copy of each of these inner variables/functions. We assign this instance tofred. If we run User()again, we'd get a new instance entirely separate from fred.

The inner doLogin() function has a closure over username and password, meaning it will retain its access to them even after the User() function finishes running.

publicAPI is an object with one property/method on it, login, which is a reference to the innerdoLogin() function. When we return publicAPI from User(), it becomes the instance we callfred.

At this point, the outer User() function has finished executing. Normally, you'd think the inner variables like usernameand password have gone away. But here they have not, because there's a closure in the login() function keeping them alive.

That's why we can call fred.login(..) -- the same as calling the innerdoLogin(..)-- and it can still access username and password inner variables.

There's a good chance that with just this brief glimpse at closure and the module pattern, some of it is still a bit confusing. That's OK! It takes some work to wrap your brain around it.

this Identifier

While it may often seem that this is related to "object-oriented patterns," in JS this is a different mechanism.

If a function has a this reference inside it, that this reference usually points to an object. But which object it points to depends on how the function was called.

function foo() {
    console.log( this.bar );
}

var bar = "global";

var obj1 = {
    bar: "obj1",
    foo: foo
};

var obj2 = {
    bar: "obj2"
};

// --------

foo();              // "global"
obj1.foo();         // "obj1"
foo.call( obj2 );   // "obj2"
new foo();          // undefined

There are four rules for how this gets set, and they're shown in those last four lines of that snippet.

  1. foo() ends up settingthisto the global object in non-strict mode -- in strict mode,thiswould beundefinedand you'd get an error in accessing thebarproperty -- so "global" is the value found forthis.bar`.
  2. obj1.foo() sets this to the obj1 object.
  3. foo.call(obj2) sets this to the obj2object.
  4. new foo() sets this to a brand new empty object.

Prototypes

When you reference a property on an object, if that property doesn't exist, JavaScript will automatically use that object's internal prototype reference to find another object to look for the property on. You could think of this almost as a fallback if the property is missing.

The internal prototype reference linkage from one object to its fallback happens at the time the object is created. The simplest way to illustrate it is with a built-in utility called Object.create(..).

var foo = {
    a: 42
};

// create `bar` and link it to `foo`
var bar = Object.create( foo );

bar.b = "hello world";

bar.b;      // "hello world"
bar.a;      // 42 <-- delegated to `foo`

It may help to visualize the foo and bar objects and their relationship:


image.png

Old & New

Some of the JS features we've already covered, and certainly many of the features covered in the rest of this series, are newer additions and will not necessarily be available in older browsers. In fact, some of the newest features in the specification aren't even implemented in any stable browsers yet.

There are two main techniques you can use to "bring" the newer JavaScript stuff to the older browsers: polyfilling and transpiling.

Polyfilling

What is a Polyfill?

if (!Number.isNaN) {
    Number.isNaN = function isNaN(x) {
        return x !== x;
    };
}
Transpiling

The better option is to use a tool that converts your newer code into older code equivalents. This process is commonly called "transpiling," a term for transforming + compiling.

Non-JavaScript

So far, the only things we've covered are in the JS language itself. The reality is that most JS is written to run in and interact with environments like browsers. A good chunk of the stuff that you write in your code is, strictly speaking, not directly controlled by JavaScript. That probably sounds a little strange.

The most common non-JavaScript JavaScript you'll encounter is the DOM API. For example:

var el = document.getElementById( "foo" );

This book, and this whole series, focuses on JavaScript the language. That's why you don't see any substantial coverage of these non-JavaScript JavaScript mechanisms. Nevertheless, you need to be aware of them, as they'll be in every JS program you write!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容