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!

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市贡羔,隨后出現(xiàn)的幾起案子勃救,更是在濱河造成了極大的恐慌,老刑警劉巖治力,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒙秒,死亡現(xiàn)場離奇詭異,居然都是意外死亡宵统,警方通過查閱死者的電腦和手機晕讲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來马澈,“玉大人瓢省,你說我怎么就攤上這事∪啵” “怎么了勤婚?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長涤伐。 經(jīng)常有香客問我馒胆,道長,這世上最難降的妖魔是什么凝果? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任祝迂,我火速辦了婚禮,結果婚禮上器净,老公的妹妹穿的比我還像新娘型雳。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布纠俭。 她就那樣靜靜地躺著沿量,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冤荆。 梳的紋絲不亂的頭發(fā)上欧瘪,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音匙赞,去河邊找鬼。 笑死妖碉,一個胖子當著我的面吹牛涌庭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播欧宜,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼坐榆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了冗茸?” 一聲冷哼從身側響起席镀,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎夏漱,沒想到半個月后豪诲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡挂绰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年屎篱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葵蒂。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡交播,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出践付,到底是詐尸還是另有隱情秦士,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布永高,位于F島的核電站隧土,受9級特大地震影響,放射性物質發(fā)生泄漏命爬。R本人自食惡果不足惜次洼,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望遇骑。 院中可真熱鬧卖毁,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至翔脱,卻和暖如春奴拦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背届吁。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工错妖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人疚沐。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓暂氯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親亮蛔。 傳聞我的和親對象是個殘疾皇子痴施,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容