函式與物件的差別在有被呼叫的能力。
用function所宣告的字詞。
function callMyName() {
console.log('Jack')
}
function fn1() {
callMyName()
}
fn1() // 'Jack'
const callMyName = function myName() {
console.log(callMyName, myName)
}
callMyName()
//ƒ myName() {
// console.log(callMyName, myName)
//} ƒ myName() {
// console.log(callMyName, myName)
//}
myName() // myName is not defined
如果在其他地方不需要呼叫到這個函式,只有在這個地方立刻使用。
以下示範例:
(function(){console.log('我執行了')}()); // '我執行了'
(function(){console.log('我執行了')})(); // '我執行了'
立即函式有以下特點
1.立刻執行
2.無法在函式外執行,範例如下
(function IIFE(){console.log('我執行了')}()); // '我執行了'
IIFE() // IIFE is not defined
3.限制函式內變數的作用域,範例如下
(function (){
var a = 'a'
console.log(a)
})();
console.log('函式外',a) // ReferenceError: a is not defined
第二個範例
function countDown(seconde) {
for(var i = 1 ; i<seconde + 1; i ++) {
setTimeout(function(){console.log(i)}, i * 1000)
}
}
countDown(3) // 4 4 4 每隔1秒出現
// 會出現上面的情況式因為var的作用域是整個for,所以i會先跑完for迴圈後最後以 i = 3 進入setTimeout
// 這個時候就可以使用立即函式限制i的作用域
function countDown(seconde) {
for(var i = 1 ; i<seconde + 1; i ++) {
(function(j){
setTimeout(function(){console.log(j)}, i * 1000)
})(i)
}
}
countDown(3)// 1 2 3 每隔1秒出現
使用物件傳參考特性
範例一
const obj = {};
(function(data){
var a = 'a'
data.a = a
console.log('立即函式1')
})(obj); // '立即函式1'
(function(data){
console.log(data.a)
})(obj); // 'a'
把全域變數傳入並附加屬性與值後,在第二個立值函式直接調用。
範例二
(function(global){
var a = 'a'
global.a = a
console.log('立即函式1')
})(window); // '立即函式1'
(function(){
console.log(a)
})(); // 'a'
以下是函式可以使用的參數
var globalVariable = '全域'
function active(par) {
var localVariable = '區域'
console.log(par, arguments, globalVariable, localVariable, this)
}
active('我是參數', '5', true, 123)
以上範例結果如下圖
function callName(name){ // 其中的name等同於傳入的參數會被宣告為變數name
var name // 由於已經宣告變數所以這行變得沒有意義。
console.log(name) // 'Jay'
name = 'May' // 需要直接賦值材能取代原來的參數
console.log(name) // 'May'
}
callName('Jay')
function callName(name){
console.log(name)
// ƒ name() {
//console.log('我是function中的function')
// }
function name() {
console.log('我是function中的function')
}
name = 'May'
console.log(name) // 'May'
}
callName('Jay')
結果如下圖
以下是步驟拆解
function callName(name){
// 準備階段
// 函數的參數比函式與變數更優先,優先度參數>函式>變數
name = 'Jay' // 這理是舉例實際上應該不是這樣
// 這個時候才輪到函式,然後就把name = 'Jay'蓋掉成function
function name() {
console.log('我是function中的function')
}
// 執行階段
console.log(name)
// ƒ name() {
//console.log('我是function中的function')
// }
name = 'May'
console.log(name) // 'May'
}
callName('Jay')
function active(i, j, k) {
console.log(i, j, k)
}
const a = 'a'
const b = 1
active(a, b) // 'a' 1 undefined
var personal = {
name:'Jack',
age: 25,
location:'Taipei'
}
function changeName(obj){
obj.name = 'May'
}
changeName(personal)
console.log(personal.name) // 'May'
簡單來說就是把函式當作參數,傳入另一個函式內執行。
// 當成參數的函式可以是匿名函式
function active(fn){
fn('參數') //傳入的函式也可以設定參數
}
active(function(pra){console.log(pra)}) // '參數'
// 當成參數的函式也可以式具名函式
function callpar(par) {
console.log(par)
}
active(callpar) // '參數'
// 兩者的差別就在匿名函式需要把完整的函式寫入參數()內,而具名函式則只要寫入函式名稱。
arguments適用在不確定會傳多少參數的函式。
function fn(a) {
console.log(a,arguments)
}
fn(1, 'a', 5) // 1 Arguments(3) [1, 'a', 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]
因為arguments是類陣列,只能使用部分的陣列功能,使用前可以轉成陣列。
範例如下
function fn(a) {
// 用展開的方式轉為陣列
const parments = [...arguments]
parments.forEach((i) =>{
console.log(i)
})
}
fn(1, 'a', 5) // 1 'a' 5
閉包就是把函式內的變數,封在函式內。
以下是範例:
function walletMoney(){
var money = 5000;
return function(price){
money = money - price
return money
}
}
walletMoney()(500) // 4500
// 使用函式表達式這樣子相當等於每一個變數都有自己的錢包。
var JayWallet = walletMoney()
console.log(JayWallet(1000)) // 4000
var MayWallet = walletMoney()
console.log(MayWallet(4500)) // 500
工廠模式
對4-1的範例進行修改
function walletMoney(initWalletMoney){
var money = initWalletMoney;
return function(price){
money = money - price
return money
}
}
// 這樣子就可以自訂每個錢包的價格。
const myWallet = walletMoney(1000)
console.log(myWallet(700)) // 300
const MayWallet = alletMoney(5000)
console.log(MayWallet(1000)) // 4000
私有化方法
對工廠模式在進行修改
function walletMoney(initWalletMoney){
// 如果沒有傳入金額則預設1000
var money = initWalletMoney || 10000;
return {
spend(price){
money = money - price
return money
},
store(number){
money = money + number
return money
},
checkWallet(){
return money
}
}
}
const myWallet = walletMoney(5000);
console.log(myWallet.checkWallet()) // 5000
console.log(myWallet.spend(500)) // 4500
console.log(myWallet.store(800)) // 5300
simple call就是直接執行函式。
以下是範例:
var name = 'Jack'
function callName() {
console.log(this.name)
}
callName() // Jack
以上的範例就是simple call。
特性是simple call的this都會指項全域。
盡可能的不要調用simple的this
var name = 'Jack'
;(function(){
var name = 'May'
console.log(this.name)
})() // 'Jack'
以下是範例
var name = 'Jack'
function callName(fn){
const name = 'May'
fn()
}
function call(){
console.log(this.name)
}
callName(call) // 'Jack'
forEach也是
const arr = ['a', 'b', 'c']
var name = 'Jack'
function callName() {
var name = 'May'
arr.forEach(function(i){
console.log(`${this.name} ${i}`)
})
}
const data = {
name:'May',
callName
}
data.callName() // 'Jack a' 'Jack b' 'Jack c'
如何修改?
const arr = ['a', 'b', 'c']
var name = 'Jack'
function callName() {
// 把call back function的外層的this指定成一個變數,
// 並在claa back function取代this
const that = this
arr.forEach(function(i){
console.log(`${that.name} ${i}`)
})
}
const data = {
name:'May',
callName
}
data.callName() // 'May a' 'May b' 'May c'
也可以這樣子改
const arr = ['a', 'b', 'c']
var name = 'Jack'
function callName() {
// 或使用箭頭函式
arr.forEach((i)=>{
console.log(`${this.name} ${i}`)
})
}
const data = {
name:'May',
callName
}
data.callName()// 'May a' 'May b' 'May c'
setTimeout的範例
var name = 'Jack'
function callName() {
setTimeout(function(){
console.log(this.name)
}, 1000)
}
const data = {
name : 'May',
callName
}
data.callName() // 'Jack'
修改方法與forEach相同如下
修改一
var name = 'Jack'
function callName() {
const that = this
setTimeout(function(){
console.log(that.name)
}, 1000)
}
const data = {
name : 'May',
callName
}
data.callName() // 'May'
修改二
var name = 'Jack'
function callName() {
setTimeout(()=>{
console.log(this.name)
}, 1000)
}
const data = {
name : 'May',
callName
}
data.callName() // 'May'
以下是範例:
var name = 'Jack'
function callName(){
let name = 'May'
return function(){
console.log(this.name)
}
}
const callSome = callName()
console.log(callSome()) // 'Jack'
可以從以上範例看到,this是指向全域中的name。
如何修改呢?
var name = 'Jack'
function callName(){
return {
name: 'May',
call(){
console.log(this.name)
}
}
}
const callSome = callName()
console.log(callSome.call()) // 'May'
最直接的方式就是不要使用simple call
call, apply, bind是拿來定義this的指向與執行函式。
call, apply, bind的範例如下
var name = 'Jack'
var obj = {
name: 'May'
}
function callName(par1, par2){
console.log(this.name, par1, par2)
}
callName.call(obj, 1, 2) // 'May' 1 2
callName.apply(obj,[1,2]) // 'May' 1 2
// bind會回傳元函數所以要有變數來被賦值
const call = callName.bind(obj, 1, 2)
call() // 'May' 1 2
// bind也可以先導入部分的參數
const call2 = callName.bind(obj, 1,)
// call2很像simple call 但不是,他的this已經在bind的時候就決定了
call2() // 'May' 1 undefined
// 可以在執行的時候在加入
call2(2) // 'May' 1 2
以call為例子
function fn(par1, par2){
console.log(this, par1, par2)
}
fn.call(1, 'par1', 'par2') // Number {1}[[Prototype]]: Number[[PrimitiveValue]]: 1 'par1' 'par2'
// 所傳入的純值會變成建構式,型別為物件
fn.call('1', 'par1', 'par2') // String {'1'} 'par1' 'par2'
fn.call(true, 'par1', 'par2') // Boolean {true} 'par1' 'par2'
//如果傳入null或是undefined則會是window
fn.call(null, 'par1', 'par2')
fn.call(undefined, 'par1', 'par2')
// Window {window: Window, self: Window, document: document, name: '', location: Location, …} 'par1' 'par2'
在嚴格模式下可以避免一些奇怪的錯誤,例如在沒有定義變數的時候賦值變數,就會直接報錯。
以及對call, apply, bind做出一些調整。
如何使用?
// 如果在全域中使用則全部的程式碼都會變成嚴格模式
'use strict'
// 也可以在各別的function中使用
function fn(){
'use strict'
a = 'a'
}
fn() // Uncaught ReferenceError: a is not defined
以call為例在嚴格模式下的範例
'use strict'
function fn(par1, par2){
console.log(this, par1, par2)
}
fn.call(1, 'par1', 'par2') // 1 'par1' 'par2'
// 所傳入的純值依舊是純值,
fn.call('1', 'par1', 'par2') // '1' 'par1' 'par2'
fn.call(true, 'par1', 'par2') // true 'par1' 'par2'
//如果傳入null或是undefined則會是window
fn.call(null, 'par1', 'par2') // null 'par1' 'par2'
fn.call(undefined, 'par1', 'par2') // undefined 'par1' 'par2'
在simple call的時候就像使用call()不傳入參數,所以simple call的本質是undefined
在行內執行事件時,這個時候的this會指向正在進行這個事件的DOM。
範例如下
<button onclick="console.log(this.dataset.index,this)" data-index='0'>clickme</button>
點擊後會出現
<!-- 0 與 <button onclick="console.log(this.dataset.index,this)" data-index="0">clickme</button> -->
另外在addEventListener的時候也會指向DOM
範例如下
<ul>
<li data-index='0'>1</li>
<li data-index='1'>2</li>
<li data-index='2'>3</li>
</ul>
.listStyle{
background: red;
color: white;
}
const list = [...document.querySelectorAll('li')]
function clickList() {
list.forEach((j, index) => {
console.log('this', this)
if (Number(this.dataset.index) === index) {
this.classList.toggle('listStyle')
} else {
j.classList.remove('listStyle')
}
})
}
list.forEach((i) => {
// 可以注意到函式clickList內的this會指向i
i.addEventListener('click', clickList)
// 把直接寫入在function內,其中的this也是指向i
i.addEventListener('click', function(){
if (Number(this.dataset.index) === index) {
this.classList.toggle('listStyle')
} else {
j.classList.remove('listStyle')
}
})
})
list.forEach((i) => {
// 這樣子寫函式clickList的this指向全域,因為clickList()為simple call
i.addEventListener('click', function(){clickList()})
})
這個時候來了一位新同事,幫我分攤了許多工作,他是從法規的顧問公司跳槽過來,大約第二個月開始就重構QA的文件,尤其是那個魔王級的專案,順便連未來怎麼做的藍圖都已經完成,又經過半年的努力把工作流程都優化,並且完成大多數文件的法規進版,不得不說這位新人事真的強,我們在沒有上過新的法規課程的情況下,靠自己蒐集的資料完成最新法規的進版,被其他人認為有上過新的法規課程,那時時候武漢肺炎疫情的關係所有法規都暫停上課了。