隨筆亂記
陸續增加中

The function of javascript is just an Object so it can has properties or functions in it.

1 Javascript built-in types

  • string
  • number
  • boolean
  • null and undefined
  • object
  • symbol (new to ES6)

2 Hoisting

可以想像成所有宣告時機(var a)都被提升到function最開始,更精確的說是scope的最開始
實際上是javascript interpreter將var a = 2分成兩個步驟
declaration(var a),assignment(a = 2)
所有declaration在compile time完成,assignment在runtime執行
須注意的是ES6新增的block scope variable =let & const=,沒有hoisting機制
當然eval("var a = 2;");這種用法也會破壞hoisting,不過為了效能本來就該避免eval這東西了

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

3 Closure

Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.

foo()執行完後,local variable a沒被回收的原因是因為baz -> bar還擁有reference to foo,所以a不會被GC回收

function foo() {
var a = 2;

function bar() {
console.log( a );
}

return bar;
}

var baz = foo();

baz(); // 2 -- Whoa, closure was just observed, man.

some useful snippet

function wait(message) {

setTimeout( function timer(){
console.log( message );
}, 1000 );

}

wait( "Hello, closure!" );

4 Immediately Invoked Function Expressions (IIFEs)

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

5 Modules

var foo = (function CoolModule(id) {
function change() {
// modifying the public API
publicAPI.identify = identify2;
}

function identify1() {
console.log( id );
}

function identify2() {
console.log( id.toUpperCase() );
}

var publicAPI = {
change: change,
identify: identify1
};

return publicAPI;
})( "foo module" );

foo.identify(); // foo module
foo.change();
foo.identify(); // FOO MODULE

5.1 Modern Modules

module dependency loaders/managers

var MyModules = (function Manager() {
var modules = {};

function define(name, deps, impl) {
for (var i=0; i<deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply( impl, deps );
}

function get(name) {
return modules[name];
}

return {
define: define,
get: get
};
})();

use above

MyModules.define( "bar", [], function(){
function hello(who) {
return "Let me introduce: " + who;
}

return {
hello: hello
};
} );

MyModules.define( "foo", ["bar"], function(bar){
var hungry = "hippo";

function awesome() {
console.log( bar.hello( hungry ).toUpperCase() );
}

return {
awesome: awesome
};
} );

var bar = MyModules.get( "bar" );
var foo = MyModules.get( "foo" );

console.log(
bar.hello( "hippo" )
); // Let me introduce: hippo

foo.awesome(); // LET ME INTRODUCE: HIPPO

5.2 Future Modules

ES6新語法,支援File based Modules

bar.js

function hello(who) {
return "Let me introduce: " + who;
}

export hello;

foo.js

// import only `hello()` from the "bar" module
import hello from "bar";

var hungry = "hippo";

function awesome() {
console.log(
hello( hungry ).toUpperCase()
);
}

export awesome;

use above

// import the entire "foo" and "bar" modules
module foo from "foo";
module bar from "bar";

console.log(
bar.hello( "rhino" )
); // Let me introduce: rhino

foo.awesome(); // LET ME INTRODUCE: HIPPO

6 This

javascript的this與一般OO的this很不一樣,他是runtime時才決定指向哪裡的
詳細決定規則請見以下

6.1 Default Binding

當不滿足以下三種狀況時,this會自動指向global,strict mode下則是undefined

6.2 Implicit Binding

this指向obj2

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

var obj2 = {
a: 42,
foo: foo
};

var obj1 = {
a: 2,
obj2: obj2
};

obj1.obj2.foo(); // 42

在callback下,容易發生implicitly lost

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

var obj = {
a: 2,
foo: foo
};

var a = "oops, global"; // `a` also property on global object

setTimeout( obj.foo, 100 ); // "oops, global"

6.3 Explicit Binding

可利用call、apply來強制綁定this物件,解決上述的implicitly lost

function foo(something) {
console.log( this.a, something );
return this.a + something;
}

// simple `bind` helper
// bind obj to this of fn
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments );
};
}

var obj = {
a: 2
};

var bar = bind( foo, obj );

var b = bar( 3 ); // 2 3
console.log( b ); // 5

ES5內建bind方法可簡單達成上述

function foo(something) {
console.log( this.a, something );
return this.a + something;
}

var obj = {
a: 2
};

var bar = foo.bind( obj );

var b = bar( 3 ); // 2 3
console.log( b ); // 5

6.4 New Binding

function foo(a) {
this.a = a;
}

var bar = new foo( 2 ); // this is binding to bar
console.log( bar.a ); // 2

6.5 So How To Determine This

  1. Is the function called with new (new binding)? If so, this is the newly constructed object.

    var bar = new foo()

  2. Is the function called with call or apply (explicit binding), even hidden inside a bind hard binding? If so, this is the explicitly specified object.

    var bar = foo.call( obj2 )

  3. Is the function called with a context (implicit binding), otherwise known as an owning or containing object? If so, this is that context object.

    var bar = obj1.foo()

  4. Otherwise, default the this (default binding). If in strict mode, pick undefined, otherwise pick the global object.

    var bar = foo()

6.6 Safer this

如果你發現你需要apply或bind的parameter spread、currying功能
但卻不care this到底是什麼,你可能會想用apply(null, [2, 3])
這可能導致this指向global(default binding)
底下是一種比較好的方式,create一個empty object來取代null

function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}

// our DMZ empty object
var ø = Object.create( null );

// spreading out array as parameters
foo.apply( ø, [2, 3] ); // a:2, b:3

// currying with `bind(..)`
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3

6.7 arrow function

ES6新增的arrow function,有著跟一般function不一樣的this行為
其this永遠指向包圍它的function的this,使其在callback中很有用

function foo() {
setTimeout(() => {
// `this` here is lexically adopted from `foo()`
console.log( this.a );
},100);
}

var obj = {
a: 2
};

foo.call( obj ); // 2

然而在不用arrow function時就已經有好解法了:

function foo() {
var self = this; // lexical capture of `this`
setTimeout( function(){
console.log( self.a );
}, 100 );
}

var obj = {
a: 2
};

foo.call( obj ); // 2

當然你也能用bind來處理callback的this,這三種方法就是程式風格的不同而已,儘量統一就好

7 Object

7.1 Computed Property Names

ES6 adds computed property names, where you can specify an expression, surrounded by a [ ] pair, in the key-name position of an object-literal declaration:

var prefix = "foo";

var myObject = {
[prefix + "bar"]: "hello",
[prefix + "baz"]: "world"
};

myObject["foobar"]; // hello
myObject["foobaz"]; // world

常配合ES6新增的Symbol使用

var myObject = {
[Symbol.Something]: "hello world"
};

7.2 Duplicating Objects

Copy Object分成shallow copy and deep copy,其中deep copy可能會出現某些問題
例如以下例子,deep copy將造成infinite circular duplication

function anotherFunction() { /*..*/ }

var anotherObject = {
c: true
};

var anotherArray = [];

var myObject = {
a: 2,
b: anotherObject, // reference, not a copy!
c: anotherArray, // another reference!
d: anotherFunction
};

anotherArray.push( anotherObject, myObject );

若欲複製的object是JSON-safe (that is, can be serialized to a JSON string and then re-parsed to an object with the same structure and values),我們可以用以下方式做到deep copy

var newObj = JSON.parse( JSON.stringify( someObj ) );

shallow copy比較沒有爭議,所以ES6提供以下方式:

var newObj = Object.assign( {}, myObject );

newObj.a; // 2
newObj.b === anotherObject; // true
newObj.c === anotherArray; // true
newObj.d === anotherFunction; // true

7.3 Property Descriptors

ES5以後才有的功能

  • writable(可否modify)
  • enumerable(可否用for…in遍歷)
  • configurable(可否用difineProperty重新定義 and 可否用delete刪除)

Get Property Descriptors

var myObject = {
a: 2
};

Object.getOwnPropertyDescriptor( myObject, "a" );
// {
// value: 2,
// writable: true,
// enumerable: true,
// configurable: true
// }

Modify Property Descriptors

var myObject = {};

Object.defineProperty( myObject, "a", {
value: 2,
writable: false, // not writable!
configurable: true,
enumerable: true
} );

myObject.a = 3;

myObject.a; // 2

若是在strict mode,上述會扔出TypeError

7.4 Immutability

以下方式均只能做到shallow immutability. That is, they affect only the object and its direct property characteristics. If an object has a reference to another object (array, object, function, etc), the contents of that object are not affected, and remain mutable.

7.4.1 Object Constant

Use writable:false and configurable:false to construct Constant Property

var myObject = {};

Object.defineProperty( myObject, "FAVORITE_NUMBER", {
value: 42,
writable: false,
configurable: false
} );

7.4.2 Prevent Extensions

var myObject = {
a: 2
};

Object.preventExtensions( myObject );

myObject.b = 3;
myObject.b; // undefined

7.4.3 Seal

Object.seal(..) creates a "sealed" object, which means it takes an existing object and essentially calls Object.preventExtensions(..) on it, but also marks all its existing properties as configurable:false.

7.4.4 Freeze

Object.freeze(..) creates a frozen object, which means it takes an existing object and essentially calls Object.seal(..) on it, but it also marks all "data accessor" properties as writable:false, so that their values cannot be changed.

若需要deep freeze,可自行recursive Object.freeze()

7.5 Getters & Setters

You will almost certainly want to always declare both getter and setter (having only one or the other often leads to unexpected/surprising behavior)

var myObject = {
// define a getter for `a`
get a() {
return this._a_;
},

// define a setter for `a`
set a(val) {
this._a_ = val * 2;
}
};

myObject.a = 2;

myObject.a; // 4

7.6 Existence

如何check object是否有某個property

var myObject = {
a: 2
};

("a" in myObject); // true
("b" in myObject); // false

myObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "b" ); // false

in與hasOwnProperty的最大不同處在in會檢查prototype chain而hasOwnProperty不會,需小心的是若object沒有 delegation to Object.prototype,他將不會擁有hasOwnProperty,此時可以使用Object.prototype.hasOwnProperty.call(myObject,"a")來檢查
另外in看起來好像可以這樣用:4 in [2, 4, 6],但實際上它檢查的是object的property,所以此用法是錯誤的,in不可用來檢查containers是否存在某值

7.7 Enumeration

var myObject = { };

Object.defineProperty(
myObject,
"a",
// make `a` enumerable, as normal
{ enumerable: true, value: 2 }
);

Object.defineProperty(
myObject,
"b",
// make `b` NON-enumerable
{ enumerable: false, value: 3 }
);

myObject.b; // 3
("b" in myObject); // true
myObject.hasOwnProperty( "b" ); // true

// .......

for (var k in myObject) {
console.log( k, myObject[k] );
}
// "a" 2

Note: for…in將會travel all values and properties(include prototype chain),所以建議遍歷object使用for…in loop,遍歷array的話使用一般for loop

var myObject = { };

Object.defineProperty(
myObject,
"a",
// make `a` enumerable, as normal
{ enumerable: true, value: 2 }
);

Object.defineProperty(
myObject,
"b",
// make `b` non-enumerable
{ enumerable: false, value: 3 }
);

myObject.propertyIsEnumerable( "a" ); // true
myObject.propertyIsEnumerable( "b" ); // false

Object.keys( myObject ); // ["a"]
Object.getOwnPropertyNames( myObject ); // ["a", "b"]

目前沒有built-in方法可做到list all keys in object include prototype chain,需要自行recursive Object.keys()

7.8 Iteration

for…in loop拿出來的東西會是object的properties,但若我們想直接拿出value呢?
ES6新增的for…of loop可辦到

var myArray = [ 1, 2, 3 ];

for (var v of myArray) {
console.log( v );
}
// 1
// 2
// 3

for…of僅能用在有定義@@iterator的object上,array預設就有,但其他object預設是沒有的
若想用for…of的話還是可以自己定義@@iterator,滿足以下即可:
As long as your iterator returns the expected { value: .. } return values from next() calls, and a { done: true } after the iteration is complete, ES6's for..of can iterate over it.

var myObject = {
a: 2,
b: 3
};

Object.defineProperty( myObject, Symbol.iterator, {
enumerable: false,
writable: false,
configurable: true,
value: function() {
var o = this;
var idx = 0;
var ks = Object.keys( o );
return {
next: function() {
return {
value: o[ks[idx++]],
done: (idx > ks.length)
};
}
};
}
} );

// iterate `myObject` manually
var it = myObject[Symbol.iterator]();
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { value:undefined, done:true }

// iterate `myObject` with `for..of`
for (var v of myObject) {
console.log( v );
}
// 2
// 3

以下是一個利用iterator的亂數產生器

var randoms = {
[Symbol.iterator]: function() {
return {
next: function() {
return { value: Math.random() };
}
};
}
};

var randoms_pool = [];
for (var n of randoms) {
randoms_pool.push( n );

// don't proceed unbounded!
if (randoms_pool.length === 100) break;
}

8 Mixing (Up) "Class" Objects

JavaScript's object mechanism does not automatically perform copy behavior when you "inherit" or "instantiate". Plainly, there's no "classes" in JavaScript to instantiate, only objects. And objects don't get copied to other objects, they get linked together.

Since observed class behaviors in other languages imply copies, let's examine how JS developers fake the missing copy behavior of classes in JavaScript: mixins. We'll look at two types of "mixin": explicit and implicit.

8.1 Explicit Mixins

Such a utility is often called extend(..) by many libraries/frameworks, but we will call it mixin(..) here for illustrative purposes.

// vastly simplified `mixin(..)` example:
function mixin( sourceObj, targetObj ) {
for (var key in sourceObj) {
// only copy if not already present
if (!(key in targetObj)) {
targetObj[key] = sourceObj[key];
}
}

return targetObj;
}

var Vehicle = {
engines: 1,

ignition: function() {
console.log( "Turning on my engine." );
},

drive: function() {
this.ignition();
console.log( "Steering and moving forward!" );
}
};

var Car = mixin( Vehicle, {
wheels: 4,

drive: function() {
Vehicle.drive.call( this );
console.log( "Rolling on all " + this.wheels + " wheels!" );
}
} );

But because of JavaScript's peculiarities, explicit pseudo-polymorphism (because of shadowing!) creates brittle manual/explicit linkage in every single function where you need such a (pseudo-)polymorphic reference. This can significantly increase the maintenance cost. Moreover, while explicit pseudo-polymorphism can emulate the behavior of "multiple inheritance", it only increases the complexity and brittleness.

The result of such approaches is usually more complex, harder-to-read, and harder-to-maintain code. Explicit pseudo-polymorphism should be avoided wherever possible, because the cost outweighs the benefit in most respects.

8.2 Implicit Mixins

var Something = {
cool: function() {
this.greeting = "Hello World";
this.count = this.count ? this.count + 1 : 1;
}
};

Something.cool();
Something.greeting; // "Hello World"
Something.count; // 1

var Another = {
cool: function() {
// implicit mixin of `Something` to `Another`
Something.cool.call( this );
}
};

Another.cool();
Another.greeting; // "Hello World"
Another.count; // 1 (not shared state with `Something`)

While this sort of technique seems to take useful advantage of this rebinding functionality, it is the brittle Something.cool.call( this ) call, which cannot be made into a relative (and thus more flexible) reference, that you should heed with caution. Generally, avoid such constructs where possible to keep cleaner and more maintainable code.

8.3 Conclusion

Classes are a design pattern. Many languages provide syntax which enables natural class-oriented software design. JS also has a similar syntax, but it behaves very differently from what you're used to with classes in those other languages.

Classes mean copies.

When traditional classes are instantiated, a copy of behavior from class to instance occurs. When classes are inherited, a copy of behavior from parent to child also occurs.

Polymorphism (having different functions at multiple levels of an inheritance chain with the same name) may seem like it implies a referential relative link from child back to parent, but it's still just a result of copy behavior.

JavaScript does not automatically create copies (as classes imply) between objects.

The mixin pattern (both explicit and implicit) is often used to sort of emulate class copy behavior, but this usually leads to ugly and brittle syntax like explicit pseudo-polymorphism (OtherObj.methodName.call(this, ...)), which often results in harder to understand and maintain code.

Explicit mixins are also not exactly the same as class copy, since objects (and functions!) only have shared references duplicated, not the objects/functions duplicated themselves. Not paying attention to such nuance is the source of a variety of gotchas.

In general, faking classes in JS often sets more landmines for future coding than solving present real problems.

9 Prototype

JavaScript沒有Class(一般Class-Oriented Language的class),只有Object
Prototype的概念比較類似Delegate而不是Inherited(Copy)
Objects in JavaScript have an internal property, denoted in the specification as Prototype, which is simply a reference to another object. Almost all objects are given a non-null value for this property, at the time of their creation.

var anotherObject = {
a: 2
};

// create an object linked to `anotherObject`
var myObject = Object.create( anotherObject );

myObject.a; // 2

9.1 Setting & Shadowing Properties

  1. If a normal data accessor property named foo is found anywhere higher on the Prototype chain, and it's not marked as read-only (writable:false) then a new property called foo is added directly to myObject, resulting in a shadowed property.
  2. If a foo is found higher on the Prototype chain, but it's marked as read-only (writable:false), then both the setting of that existing property as well as the creation of the shadowed property on myObject are disallowed. If the code is running in strict mode, an error will be thrown. Otherwise, the setting of the property value will silently be ignored. Either way, no shadowing occurs.
  3. If a foo is found higher on the Prototype chain and it's a setter (see Chapter 3), then the setter will always be called. No foo will be added to (aka, shadowed on) myObject, nor will the foo setter be redefined.
var anotherObject = {
a: 2
};

var myObject = Object.create( anotherObject );

anotherObject.a; // 2
myObject.a; // 2

anotherObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "a" ); // false

myObject.a++; // oops, implicit shadowing!

anotherObject.a; // 2
myObject.a; // 3

myObject.hasOwnProperty( "a" ); // true

Shadowing with methods leads to ugly explicit pseudo-polymorphism if you need to delegate between them. Usually, shadowing is more complicated and nuanced than it's worth, so you should try to avoid it if possible. Use Behavior Delegation for an alternative design pattern, which among other things discourages shadowing in favor of cleaner alternatives.

9.2 New(Constructor Call)

  1. a brand new object is created (aka, constructed) out of thin air
  2. the newly constructed object is Prototype-linked
  3. the newly constructed object is set as the this binding for that function call
  4. unless the function returns its own alternate object, the new-invoked function call will automatically return the newly constructed object.

JS沒有Class也沒有Constructor,new的概念跟一般Class-Oriented Language完全不同
New可以放在任何function前,此時被稱為Constructor Call
var a = new Foo()代表:
建立新object a,a的_proto_指向Foo.prototype,this綁定到該a上

function Foo() {
// ...
}

var a = new Foo();

Object.getPrototypeOf( a ) === Foo.prototype; // true

利用prototype模擬Class instantiate Object
看起來很像Class-Oriented,但實際上是利用上述機制

function Foo(name) {
this.name = name;
}

Foo.prototype.myName = function() {
return this.name;
};

var a = new Foo( "a" );
var b = new Foo( "b" );

a.myName(); // "a"
b.myName(); // "b"

9.3 constructor

function Foo() {
// ...
}

Foo.prototype.constructor === Foo; // true

var a = new Foo();
a.constructor === Foo; // true

看起來好像a擁有一個property指向Foo,實際上卻是a._proto__.constructor
此constructor指向Foo看似很有用實際上卻可被複寫,*因此別依賴這個特性*

function Foo() { /* .. */ }

Foo.prototype = { /* .. */ }; // create a new prototype object

var a1 = new Foo();
a1.constructor === Foo; // false!
a1.constructor === Object; // true!

9.4 (Prototypal) Inheritance

function Foo(name) {
this.name = name;
}

Foo.prototype.myName = function() {
return this.name;
};

function Bar(name,label) {
Foo.call( this, name );
this.label = label;
}

// here, we make a new `Bar.prototype`
// linked to `Foo.prototype`
Bar.prototype = Object.create( Foo.prototype );

// Beware! Now `Bar.prototype.constructor` is gone,
// and might need to be manually "fixed" if you're
// in the habit of relying on such properties!

Bar.prototype.myLabel = function() {
return this.label;
};

var a = new Bar( "a", "obj a" );

a.myName(); // "a"
a.myLabel(); // "obj a"

Object.create(..) creates a "new" object out of thin air, and links that new object's internal Prototype to the object you specify (Foo.prototype in this case).

底下是兩個錯誤用法

// doesn't work like you want!
// 這樣的話根本不需要多一個Bar,用Foo就好了
Bar.prototype = Foo.prototype;

// works kinda like you want, but with
// side-effects you probably don't want :(
// 若Foo()裡面有做一些其他事,會一起做,但是我們只是要建立prototype chain
Bar.prototype = new Foo();

ES6後可以用Object.setPrototypeOf()代替Object.create()

// pre-ES6
// throws away default existing `Bar.prototype`
Bar.prototype = Object.create( Foo.prototype );

// ES6+
// modifies existing `Bar.prototype`
Object.setPrototypeOf( Bar.prototype, Foo.prototype );

10 Behavior Delegation

以下都先參考原文吧,我有空在整理
主要是說在javascript世界中Behavior Delegation的做法比OO還要直覺簡單,有實際例子、code舉例
You-Dont-Know-JS/this & object prototypes/ch6.md

11 Asynchrony

大致介紹javascript的Asynchronous
JS Engine本身沒有multi-thread,javascript的Asynchronous是靠run environment的event loop queue達成
You-Dont-Know-JS/async & performance/ch1.md

12 Callbacks

主要是在說明傳統使用callback來達成Asynchronous的各種缺點

  1. 反人類思考模式,難以閱讀
  2. callback會將程式帶到非自己所寫的function,可以在callback中撰寫各種check來處理但不夠好

You-Dont-Know-JS/async & performance/ch2.md

13 ES6新東東

let, const, arrow function, class, module, symbol

14 Reference

此筆記主要是來自截取並整理自Github上的You-Dont-Know-JS
附上原始位置供各位參考
https://github.com/getify/You-Dont-Know-JS

Last Updated 2017-03-10 五 10:26.
Render by hexo-renderer-org with Emacs 25.2.1 (Org mode 8.2.10)