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

constlet

{
    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.jsb.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"/>

缺點

  1. 按照順序載入,你想要更動順序時你要對每一個 js 有一定程度的了解
  2. 誰要維護這份列表?

AMD 與 CommonJS

CommonJS

  1. 同步 Loading (module A 讀完才開始讀 module B
  2. Node.js 使用這規範
  3. 每一份JS 文件就是一個 Module,獨立作用域
  4. JS 文件對於其它檔案來說是不可見的

AMD

  1. Asynchronous Module Definition
  2. requireJS  為該規範的實做
  3. 用在前端網頁

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

  1. 強制採用嚴格模式 "use strict"

  2. 必須在文件最上方 import (requireJs 可以在任一地方)

  3. 重複 import 只會執行一次


     

  4. 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

  1. 使用 ES6 的標準,透過 Babel 轉換成 CommonJs
  2. 在 Node.JS 中直接使用 (副檔名要改成 .mjs)
  3. 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:

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