Javascript Introduction
Lasted updated: 06/03/2024
part 2
Lexical scope
作用域
作用域 (Scope)
function foo(){
var a = 10;
}
a;
ReferenceError: a is not defined
作用域 (Scope)
var a = 10;
function foo(){
console.log(a);
}
foo(); // 10
作用域 (Scope)
function outer(){
var a = 10;
function inner(){
var b = 20;
console.log(a);
}
inner(); // 10
console.log(b);
// ReferenceError: b is not defined
}
outer();
作用域 (Scope)
在
ES6 之前
,
唯一
會產生作用域的地方為
function
inner function 可以取用 outer function 的東西,反過來就不行
function outer(){
var a = 10;
function inner(){
var b = 20;
}
}
對 inner 來說 a 不是自己作用域裡的東西,它被稱為 free variable (自由變數)
作用域 (Scope)
var a = 10;
function logA(){
console.log(a);
}
function bar(){
var a = 30;
logA();
}
bar();
// 10
Javascript 採靜態作用域 static scope (lexical scope)
作用域 (Scope)
var a = 20;
if(true){
var b = 30;
}
console.log(b);
// 30
if ... else structure
作用域 (Scope)
for(var a = 0; a < 10; a++){}
console.log(a); // 10
for(var i = 0; i < 10; i++){
for(var j = 0; j < 10; j++){
// ...
}
}
console.log(i, j); // 10, 10
for...loop structure
作用域 (Scope) in ES6
- let 、 const
- 不會受到 hoisting 影響
- 多了塊級作用域 (Block Scope)
function foo(){
return a;
let a = 10;
}
foo(); // ReferenceError: a is not defined
{
let b = 100;
}
b; // ReferenceError: b is not defined
作用域 (Scope) in ES6
- let 、 const
- 不會受到 hoisting 影響
- 多了塊級作用域 (Block Scope)
for(let i =0; i < 10; i++){}
i; // ReferenceError: i is not defined
if(true){
let abc = 123;
}
abc; // ReferenceError: abc is not defined
const、let
{
const PI = 3.14;
}
PI; // ReferenceError: PI is not defined
const FOO = 10;
FOO = 20;
// TypeError: Assignment to constant variable.
const 與 let 行為一致,只差在 const 不能再度賦值
Closures
閉包
所以什麼是 closure?
wiki: A closure is a record storing a function together with an environment
建立 Closure
一般情況,local variable 只存活在執行時的當下
function makeClosure(){
var a = 10;
}
makeClosure();
a; // ReferenceError: a is not defined
A function that returns a closure function
function makeClosure(){
var a = 10;
var getA = function(){
return a;
}
return getA;
}
var getA = makeClosure();
getA(); // 10
A
closure
is created each time
makeClosure
returns.
建立 Closure (1/3)
A function that returns a closure function
function makeClosure(){
var a = 10;
return function(){
return a;
}
}
var getA = makeClosure();
getA(); // 10
A
closure
is created each time
makeClosure
returns.
建立 Closure (2/3)
A function that returns a closure function ... with IIFE
var getA = (function(){
var a = 10;
return function(){
return a;
}
})()
getA(); // 10
建立 Closure (3/3)
現在有一個網頁 index.html,載入了兩份 js: a.js、 b.js
<html>
<head>
<script src="a.js"/>
<script src="b.js"/>
</head>
<body>
</body>
</html>
好處...?
// a.js
var count = 0;
function addItem(item){
// do something...
count +=1;
}
addItem(item);
addItem(item);
addItem(item);
count; // 3
a.js在做新增購物清單的程式:
// b.js
var count = 0;
function addFavorite(url){
// do something...
count +=1;
}
addFavorite(url);
count; // 2
b.js新增我的最愛項目:
兩個檔案都用了相同名稱的變數 count ,a.js 的被覆蓋
// b.js
// some where
console.log('item count:' + count);
// item count: 2
用 closure 改寫
// a.js
var addItem = (function(){
var count = 0;
return function(item){
// do something...
count +=1;
}
})();
// b.js
var addFavorite = (function(){
var count = 0;
return function(url){
// do something
count +=1;
}
})();
那 a.js、b.js 的 function 都取 addItem 怎麼辦?
namespace
模組化 (modular)
namespace
window.eShop = window.eShop || {};
// a.js
eShop.shoppingCart = {};
eShop.shoppingCart.addItem = //...
// b.js
eShop.favorite= {};
eShop.favorite.addItem = //...
Modular
腳本依賴
<html>
<head>
<script src="a.js"/>
<script src="b.js"/>
</head>
<body>
</body>
</html>
腳本依賴
<script src="a.js"/>
<script src="b.js"/>
缺點
- 按照順序載入,你想要更動順序時你要對每一個 js 有一定程度的了解
- 誰要維護這份列表?
AMD 與 CommonJS
CommonJS
- 同步 Loading (module A 讀完才開始讀 module B
- Node.js 使用這規範
- 每一份JS 文件就是一個 Module,獨立作用域
- JS 文件對於其它檔案來說是不可見的
AMD
- Asynchronous Module Definition
- requireJS 為該規範的實做
- 用在前端網頁
Defined Module
AMD 與 CommonJS
// a.js
var someVariable = 123;
function add(a, b){
return a + b;
}
module.exports = {
add,
}
const ModuleA = require('./a');
ModuleA.add(10, 20); // 30
CommonJs
CommonJS的載入機制,輸入的是被輸出的值的 copy
也就是說一旦輸出,模塊內部的變化就影響不到這個值。
Defined Module
AMD 與 CommonJS
// a.js
var someVariable = 123;
function add(a, b){
return a + b;
}
module.exports = {
add,
}
const ModuleA = require('./a');
ModuleA.add(10, 20); // 30
CommonJs
Defined Module
AMD 與 CommonJS
// a.js
define(function(){
var someVariable;
return {
add: function(a,b){
return a + b;
}
}
});
// b.js
define(['m1','m2'],function(m1,m2){
//...
return {
// export
}
});
AMD (1/2)
Defined Module
AMD 與 CommonJS
//AMD Wrapper
define(
["types/Employee"], //依赖
function(Employee){ //这个回调会在所有依赖都被加载后才执行
function Programmer(){
//do something
};
Programmer.prototype = new Employee();
return Programmer; //return Constructor
}
)
AMD (2/2)
兩者兼容
AMD 與 CommonJS
// https://github.com/kmdjs/observejs
if (typeof module != 'undefined' && module.exports && this.module !== module) {
module.exports = observe;
} else if (typeof define === 'function' && define.amd) {
define(observe);
} else {
win.observe = observe;
}
ES6 Module
// a.js
function add(a, b){
return a + b;
}
// rename able
exports default { add as myAdd }
// b.js
import { myAdd } from'path/xxxx/a.js' // 瀏覽器中要加付檔名
add(10, 20); // 30
ES6 Module
-
強制採用嚴格模式 "use strict"
-
必須在文件最上方 import (requireJs 可以在任一地方)
-
重複 import 只會執行一次
-
import 是 Singleton
import 'lodash';
import 'lodash';
import { foo } from 'my_module';
import { bar } from 'my_module';
// equal
import { foo, bar } from 'my_module'
ES6 Module
// a.js
function add(a, b){
return a + b;
}
function mult(a,b){
return a * b
}
// rename able
exports default { add , mult}
// import one function
import {add} from 'MyMath';
// import all
import * as MyMath from 'MyMath';
MyMath.add(1, 2); // 3
Can I Use?
ES6 Module
怎麼用
ES6 Module
- 使用 ES6 的標準,透過 Babel 轉換成 CommonJs
- 在 Node.JS 中直接使用 (副檔名要改成 .mjs)
- http://es6.ruanyifeng.com/#docs/module
"this"
this 指向於調用該函式之物件
var obj = {
name: 'Jeff',
getName: function(){
return this.name;
}
};
obj.getName();
調用函式的物件
this 指向全域物件 (global)
var x = 10;
function foo () {
console.log(this.x);
}
foo(); //10
嚴格模式下 this 指向 undefined
this 指向 new 創造出來的物件
var Person = function(name){
this.name = name;
};
Person.prototype.sayMyName = function(){
return this.name;
};
var jeff = new Person('Jeff');
jeff.sayMyName(); // "Jeff"
傳遞 this
var Person = function(name){
this.name = name;
};
var sayMyName = function(target){
return target.name;
}
var jeff = new Person('Jeff');
sayMyName(jeff ); // "Jeff"
bind
call
apply
apply
call
bind
改變執行對象(function) 的 this
fn. call ( context , arg1, arg2,... );
fn. apply ( context , [ arg1, arg2,... ]);
fn. bind ( context, arg1, arg2 );
回傳一個新的 function 並綁定 context (this)
apply
call
改變執行對象(function) 的 this
fn. call ( context , arg1, arg2,... );
var jeff = {
name: 'Jeff',
};
var sayMyName = function(greeted, separator){
return greeted + separator + this.name;
};
sayMyName.call(jeff, 'Hi!', ' , ');
// "Hi! , Jeff"
apply
call
改變執行對象(function) 的 this
var jeff = {
name: 'Jeff',
};
var sayMyName = function(greeted, separator){
return greeted + separator + this.name;
};
sayMyName.apply(jeff, ['Hi!', ' , ']);
// "Hi! , Jeff"
fn. apply ( context , [ arg1, arg2,... ]);
var jeff = {
name: 'Jeff',
};
var sayMyName = function(greeted, separator){
return greeted + separator + this.name;
};
var sayMyNameWithJeff = sayMyName.bind(jeff);
sayMyNameWithJeff('Hi!', ' , ');
// Hi! , Jeff
fn. bind ( context, arg1, arg2 );
回傳一個新的 function 並綁定 context (this)
bind
var jeff = {
name: 'Jeff',
};
var sayMyName = function(greeted){
return greeted + this.name;
};
var sayHiWithJeff =
sayMyName.bind(jeff, 'Hi! ');
var sayHeyWithJeff =
sayMyName.bind(jeff, 'Hey! ');
sayHiWithJeff(); // Hi! Jeff
sayHeyWithJeff(); // Hey! Jeff
bind 強大的地方在於,你可以衍生出不同的 function
bind
回傳一個新的 function 並綁定 context (this)
var eventCenterRegister = function(callback){
// ...
callback();
}
var player = {
userId: 87,
logout: function(){
webApiLogout(this.userId);
}
};
eventCenterRegister(player.logout.bind(player));
fn. bind ( context, arg1, arg2 );
綜合應用 (1/2)
var eventCenterRegister = function(callback, context){
// ...
callback.call(context);
};
var player = {
userId: 87,
logout: function(){
webApiLogout(this.userId);
}
};
eventCenterRegister(player.logout, player));
fn. call ( context , arg1, arg2,... );
綜合應用 (2/2)
ES6
Arrow function expression
=>
一般 Function 的寫法
var add = function(a, b){
return a + b;
};
掐指一算,這邊有 46個字元
var add = (a, b) => a + b;
剩下 26 個字
Arrow function expression
var add = (a, b) => { return a + b; }
Arrow function expression
var getObj = (name) => { return {name: name}};
getObj('Jeff'); // {name: Jeff}
當回傳物件要省略 return 時
var getObj = (name) => ({name: name});
getObj('Jeff'); // {name: Jeff}
當回傳物件要省略 return 時
key 與 value 變數同名
var getObj = name => ({name});
getObj('Jeff'); // {name: Jeff}
當回傳物件要省略 return 時
key 與 value 變數同名, ES6 中可以省略
只有一個變數,省略括號
var ary = [1, 2, 3];
ary.forEach(element => console.log(element));
// vs
ary.forEach(function(element){
console.log(element);
});
array.forEach
Arrow function auto biding this
var player = {
name: 'Jeff',
delaySayHi: function(){
setTimeout(function(){
console.log('Hi! '+ this.name);
}, 1000)
}
};
player.delaySayHi();
// 1 second later...
// "Hi! undefined"
setTimeout 的執行環境 (lexical scope) 在 global
Arrow function auto biding this
var player = {
name: 'Jeff',
delaySayHi: function(){
setTimeout(function(){
console.log('Hi! '+ this.name);
}.bind(this), 1000)
}
};
player.delaySayHi();
// 1 second later...
// "Hi! Jeff"
Arrow function auto biding this
var player = {
name: 'Jeff',
delaySayHi: function(){
setTimeout(() => {
console.log('Hi! '+ this.name);
}, 1000)
}
};
player.delaySayHi();
// 1 second later...
// "Hi! Jeff"
var player = {
name: 'Jeff',
delaySayHi(){
setTimeout(() => {
console.log('Hi! '+ this.name);
}, 1000)
}
};
player.delaySayHi();
// 1 second later...
// "Hi! Jeff"
ES6 中,Object's method 可以省略 ": function"
var player = {
name: 'Jeff',
delaySayHi(){
setTimeout(() => {
console.log('Hi! '+ this.name);
}, 1000)
}
};
player.delaySayHi();
// 1 second later...
// "Hi! Jeff"
ES6 中,Object's method 可以省略 ": function"
window.name = 'ohoh';
var user = {
name: 'Jeff',
sayMyName: ()=>{
return this.name;
}
};
user.sayMyName(); // "ohoh";
Arrow function 陷阱
Arrow function 綁定的 this 是在程式建立的當下決定的
The End
Quiz
Q1:
// 在 jsFiddle 執行下面程式碼
for(var i = 0; i < 5; i++) {
var button = document.createElement('button');
button.innerHTML = i;
button.onclick = function(){
let span = document.getElementById('now_click');
span.innerHTML = `buttonId: ${i}`;
};
document.body.appendChild(button);
}
// 這段程式碼建立了五個按鈕,並且按下時會顯示按鈕的 id
// 現在顯示的 id 是錯誤的
// 請修正它並解釋為什麼會這樣
Q1:
Q2:
Quiz - javascript scope
Q2:
const x = 3;
const foo = {
x: 2,
baz: {
x: 1,
bar: function() {
return this.x;
}
}
}
const go = foo.baz.bar;
go(); // 這裡會得到什麼值?
foo.baz.bar(); //這裡會得到什麼?
Q3:
const x = 3;
const foo = {
x: 2,
bar(){
return this.x;
},
baz: ()=> {
return thix.x;
}
}
const fooBar = foo.bar;
const fooBaz = foo.baz;
// 會印出什麼結果?
console.log(fooBar());
console.log(fooBaz());
Q4:
{
let message = 'hi';
}
function greeting(){
return message;
}
console.log(message);
Q5:
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
});
}
// 會在 console 看到什麼?
Q6:
function hey() {
for(a = 0; a < 3; a++){
console.log(1);
}
}
hey();
// 會印出什麼?
console.log(a);
Js Intro part 2
By mangogan
Js Intro part 2
- 574