1. 函數(shù)的定義和調(diào)用
1.1 函數(shù)的定義方式
- 方式1 使用
function關(guān)鍵字 (命名函數(shù))
function fn(){}
- 方式2 函數(shù)表達(dá)式(匿名函數(shù))
var fn = function(){}
- 方式3
new Function()
ar f = new Function('a', 'b', 'console.log(a + b)');
f(1, 2);
var fn = new Function('參數(shù)1','參數(shù)2'..., '函數(shù)體')
注意
/*Function 里面參數(shù)都必須是字符串格式
第三種方式執(zhí)行效率低,也不方便書寫,因此較少使用
所有函數(shù)都是 Function 的實例(對象)
函數(shù)也屬于對象
*/
1.2 函數(shù)的調(diào)用
// 1.對象的方法
let obj = {
sayHi: function () {
console.log('hi');
}
}
// 2.普通函數(shù)
function f() {
console.log('我是一個函數(shù)');
}
// 函數(shù)的調(diào)用方式 直接調(diào)用或者 使用call()調(diào)用
f.call();
// 3.構(gòu)造函數(shù)
function Star(name, age) {
this.name = name;
this.age = age;
}
// 4.綁定事件函數(shù)
let btn = document.querySelector('button');
btn.onclick = function () {
console.log('你點擊了我');
}
// 5. 定時器函數(shù)
setInterval(() => {
alert('我是函數(shù)');
}, 1000);
// 6. 立即執(zhí)行函數(shù)
(function () {
console.log('我是立即執(zhí)行函數(shù)');
})()
2. this
2.1 函數(shù)內(nèi)部的this指向
這些
this的指向,是當(dāng)我們調(diào)用函數(shù)的時候確定的。調(diào)用方式的不同決定了this的指向不同。一般指向我們的調(diào)用者。

// 1.對象的方法 this 指向的是實例對象
let obj = {
sayHi: function () {
console.log('hi');
}
}
// 2.普通函數(shù) this指向的是window
function f() {
console.log('我是一個函數(shù)');
}
// 函數(shù)的調(diào)用方式 直接調(diào)用或者 使用 call() 調(diào)用
f.call();
// 3.構(gòu)造函數(shù) this 指向的是實例對象
function Star(name, age) {
this.name = name;
this.age = age;
}
// 4.綁定事件函數(shù) this 指向的是綁定事件的對象
let btn = document.querySelector('button');
btn.onclick = function () {
console.log('你點擊了我');
};
// 5. 定時器函數(shù) 里面的this 指向的是 window
window.setInterval(() => {
alert('我是函數(shù)');
}, 1000);
// 6. 立即執(zhí)行函數(shù) 指向的是window
(function () {
console.log('我是立即執(zhí)行函數(shù)');
})();
2.2 改變函數(shù)內(nèi)部 this 指向
call方法
call()方法調(diào)用一個對象。簡單理解為調(diào)用函數(shù)的方式,但是它可以改變函數(shù)的this指向。應(yīng)用場景: 經(jīng)常做繼承。
/**
* bind()
*
* apply()
*
* call()
*/
let obj = {
name: 'andy'
};
function fun(a,b) {
console.log(this);
console.log(a + b);
}
// call 可以調(diào)用函數(shù)可以改變this的指向
fun.call(obj, 123, 123);
// call 的主要 作用可以實現(xiàn)繼承
/**
*
* @param name
* @param age
* @param sex
* @constructor
*/
function Father(name,age,sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
/**
*
* @param name
* @param age
* @param sex
* @constructor
*/
function Son(name, age, sex) {
Father.call(this, name, age, sex);
}
- 以上代碼的運行結(jié)果:

apply方法
apply()方法調(diào)用一個函數(shù)。簡單理解為調(diào)用函數(shù)的方式,但是它可以改變函數(shù)的this指向。應(yīng)用場景: 經(jīng)常跟數(shù)組有關(guān)系。
let o = {
name: 'andy'
};
function fn() {
console.log(this);
}
fn.apply(o, ['pink']);
// 1. apply也是調(diào)用函數(shù),第二個可以改變函數(shù)內(nèi)部的this指向
// 2. 但是它的參數(shù)必須是數(shù)組
// 3. apply的主要應(yīng)用比如說我們可以利用apply借助于數(shù)學(xué)內(nèi)置對象求最大值
let arr = [1, 2, 3, 512, 456];
// 使用數(shù)學(xué)內(nèi)置對象求數(shù)組中的最值
let max = Math.max.apply(Math, arr);
let min = Math.min.apply(Math, arr);
console.log(max);
console.log(min);

bind方法
bind()方法不會調(diào)用函數(shù),但是能改變函數(shù)內(nèi)部this指向,返回的是原函數(shù)改變this之后產(chǎn)生的新函數(shù)。如果只是想改變
this指向,并且不想調(diào)用這個函數(shù)的時候,可以使用bind。應(yīng)用場景:不調(diào)用函數(shù),但是還想改變
this指向。
// bind 捆綁的意思
let o = {
name: 'andy'
}
function fn(a, b) {
console.log(this);
console.log(a + b);
}
// bind 不會調(diào)用原來的函數(shù)但是可以改變原來函數(shù)內(nèi)部的 this 指向
let f = fn.bind(o, 1, 2);
f();
- 以上代碼運行結(jié)果:

bind 方法在實際開發(fā)中的使用:
- 讓按鈕在 3秒 后按鈕 變得可用。
// 1. 如果有的函數(shù)我們不需要立即調(diào)用,但是又想改變這個函數(shù)的內(nèi)部this指向此時用bind
// 2. 讓按鈕在 3秒后按鈕 變得可用
let btn = document.querySelector('button');
/**
*
* 使用笨辦法
* */
/* btn.onclick = function () {
this.disabled = true;
/!**
* 2 秒之后按鈕恢復(fù)
*!/
setTimeout(function () {
// 原來 定時器里面的 this 是指向的 window
// 現(xiàn)在改變了指向了
btn.disabled = false;
}, 2000)
};*/
/**
*
* 為按鈕定義一個單擊事件
* */
btn.onclick = function () {
this.disabled = true;
/**
* 2 秒之后按鈕恢復(fù)
*/
setTimeout(function () {
// 原來 定時器里面的 this 是指向的 window
// 現(xiàn)在改變了指向了
this.disabled = false;
}.bind(this), 2000)
};
bind 處理多個對象中this的指向問題
// 獲取所有的按鈕
let btns = document.querySelectorAll('button');
for (let i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
this.disabled = true;
setTimeout(function () {
// btns[i].disabled = false;
// bind改變了this的指向
this.disabled = false;
// 此時的this指向的時btns[i]
}.bind(this), 2000);
};
}
2.3 call、apply、bind三者的異同
共同點 : 都可以改變this指向。
不同點:
call和apply會調(diào)用函數(shù), 并且改變函數(shù)內(nèi)部this指向。call和apply傳遞的參數(shù)不一樣,call傳遞參數(shù)使用逗號隔開,apply使用數(shù)組傳遞。bind不會調(diào)用函數(shù), 可以改變函數(shù)內(nèi)部this指向。
應(yīng)用場景
call經(jīng)常做繼承. ;apply經(jīng)常跟數(shù)組有關(guān)系. 比如借助于數(shù)學(xué)對象實現(xiàn)數(shù)組最大值最小值;bind不調(diào)用函數(shù),但是還想改變this指向. 比如改變定時器內(nèi)部的this指向。
3. 嚴(yán)格模式
3.1 什么是嚴(yán)格模式
JavaScript 除了提供正常模式外,還提供了嚴(yán)格模式(strict mode)。ES5的嚴(yán)格模式是采用具有限制性 JavaScript變體的一種方式,即在嚴(yán)格的條件下運行JS代碼。
嚴(yán)格模式在IE10 以上版本的瀏覽器中才會被支持,舊版本瀏覽器中會被忽略。
嚴(yán)格模式對正常的JavaScript語義做了一些更改:
消除了
Javascript語法的一些不合理、不嚴(yán)謹(jǐn)之處,減少了一些怪異行為。消除代碼運行的一些不安全之處,保證代碼運行的安全。
提高編譯器效率,增加運行速度。
禁用了在
ECMAScript的未來版本中可能會定義的一些語法,為未來新版本的Javascript做好鋪墊。比如一些保留字如:class,enum,export, extends, import, super不能做變量名。
3.2 開啟嚴(yán)格模式
嚴(yán)格模式可以應(yīng)用到整個腳本或個別函數(shù)中。因此在使用時,我們可以將嚴(yán)格模式分為為腳本開啟嚴(yán)格模式和為函數(shù)開啟嚴(yán)格模式兩種情況。
- 情況一 :為腳本開啟嚴(yán)格模式:有的
script腳本是嚴(yán)格模式,有的script腳本是正常模式,這樣不利于文件合并,所以可以將整個腳本文件放在一個立即執(zhí)行的匿名函數(shù)之中。這樣獨立創(chuàng)建一個作用域而不影響其他script腳本文件。
// 開啟嚴(yán)格模式 下面的js代碼就會按照嚴(yán)格模式執(zhí)行代碼
'use strict';
// 但是嚴(yán)格模式只會在IE10以上才會被支持
- 情況二: 為函數(shù)開啟嚴(yán)格模式:要給某個函數(shù)開啟嚴(yán)格模式,需要把“use strict”; (或 'use strict'; ) 聲明放在函數(shù)體所有語句之前。
function fn(){
"use strict";
return "123";
}
//當(dāng)前fn函數(shù)開啟了嚴(yán)格模式
3.4 嚴(yán)格模式中的變化
- 嚴(yán)格模式對
Javascript的語法和行為,都做了一些改變。
'use strict'
num = 10
console.log(num)//嚴(yán)格模式后使用未聲明的變量
--------------------------------------------------------------------------------
var num2 = 1;
delete num2;//嚴(yán)格模式不允許刪除變量
--------------------------------------------------------------------------------
function fn() {
console.log(this); // 嚴(yán)格模式下全局作用域中函數(shù)中的 this 是 undefined
}
fn();
---------------------------------------------------------------------------------
function Star() {
this.sex = '男';
}
// Star();嚴(yán)格模式下,如果 構(gòu)造函數(shù)不加new調(diào)用, this 指向的是undefined 如果給他賦值則 會報錯.
var ldh = new Star();
console.log(ldh.sex);
----------------------------------------------------------------------------------
setTimeout(function() {
console.log(this); //嚴(yán)格模式下,定時器 this 還是指向 window
}, 2000);
4. 高階函數(shù)
高階函數(shù)是對其他函數(shù)進(jìn)行操作的函數(shù),它接收函數(shù)作為參數(shù)或?qū)⒑瘮?shù)作為返回值輸出。此時
fn就是一個高階函數(shù):函數(shù)也是一種數(shù)據(jù)類型,同樣可以作為參數(shù),傳遞給另外一個參數(shù)使用。最典型的就是作為回調(diào)函數(shù)。
同理函數(shù)也可以作為返回值傳遞回來。
// 高階函數(shù)是對其他函數(shù)進(jìn)行操作的函數(shù),它是接收函數(shù)作為參數(shù)或者將函數(shù)作為返回值輸出
function fn(a, b, callback) {
console.log(a + b);
callback && callback();
}
fn(1, 2, function () {
console.log('我是回調(diào)函數(shù),在最后調(diào)用');
});
5. 閉包
5.1 變量的作用域復(fù)習(xí)
變量根據(jù)作用域的不同分為兩種:全局變量和局部變量。
函數(shù)內(nèi)部可以使用全局變量。
函數(shù)外部不可以使用局部變量。
當(dāng)函數(shù)執(zhí)行完畢,本作用域內(nèi)的局部變量會銷毀。
5.2 什么是閉包
- 閉包(
closure)指有權(quán)訪問另一個函數(shù)作用域中變量的函數(shù)。簡單理解就是 ,一個作用域可以訪問另外一個函數(shù)內(nèi)部的局部變量。
/**
* 閉包(closure) :指的是有權(quán)訪問另一個函數(shù)作用域中變量的函數(shù)
* 簡單理解:一個作用域 fn 可以訪問另一個函數(shù) fun 內(nèi)部的局部變量
*/
function fn() {
// fn 就是一個閉包函數(shù)
let num = 10;
function fun() {
// fun 訪問了 fn 內(nèi)部的 一個局部變量 所以 fn 就是一個局部變量
console.log(num);
// fun 作用域中訪問了另一個作用域的num num 變量所在的作用域就是閉包 閉包是一種現(xiàn)象
}
fun();
}
fn();
5.3 閉包的作用
- 作用:延伸變量的作用范圍。
/**
* 我們fn外邊的作用域可以訪問fn內(nèi)部的局部變量
*
* 閉包的主要作用 : 延伸了變量的作用范圍
*/
function fn() {
let num = 10;
function fun() {
console.log(num);
}
return fun;
}
let f = fn();
/**
* 執(zhí)行之后就會產(chǎn)生閉包
* */
f();
/*
// 類似于
let f = function fun() {
console.log(num);
}
*/
5.4 閉包的案例
- 利用閉包的方式得到當(dāng)前
li的索引號;
for (var i = 0; i < lis.length; i++) {
// 利用for循環(huán)創(chuàng)建了4個立即執(zhí)行函數(shù)
// 立即執(zhí)行函數(shù)也成為小閉包因為立即執(zhí)行函數(shù)里面的任何一個函數(shù)都可以使用它的i這變量
(function(i) {
lis[i].onclick = function() {
console.log(i);
}
})(i);
}
- 閉包應(yīng)用- 3秒鐘之后,打印所有
li元素的內(nèi)容;
// 閉包案例:點擊你輸出當(dāng)前l(fā)i的索引號
var lis = document.querySelectorAll('li');
// 1. 利用我們動態(tài)添加屬性的方式
/* var lis = document.querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = function () {
/!**
* 點擊事件是異步執(zhí)行的
*!/
console.log(i); // 6 這里始終都是 6
}
}*/
// 解決:使用動態(tài)添加屬性的方式
/*for (var i = 0; i < lis.length; i++) {
lis[i].index = i; // 先將index 存一份到每一個li項中
lis[i].onclick = function () {
/!**
* 點擊事件是異步執(zhí)行的
*!/
console.log(this.index); // 6 這里始終都是 6
}
}*/
// 2. 利用閉包的方式得到當(dāng)前小li的索引號
for (let i = 0; i < lis.length; i++) {
(function (i) {
lis[i].onclick = function () {
console.log(i);
}
})(i)
}
- 閉包應(yīng)用-計算打車價格
/*需求分析
打車起步價13(3公里內(nèi)), 之后每多一公里增加 5塊錢. 用戶輸入公里數(shù)就可以計算打車價格
如果有擁堵情況,總價格多收取10塊錢擁堵費*/
var car = (function() {
var start = 13; // 起步價 局部變量
var total = 0; // 總價 局部變量
return {
// 正常的總價
price: function(n) {
if (n <= 3) {
total = start;
} else {
total = start + (n - 3) * 5
}
return total;
},
// 擁堵之后的費用
yd: function(flag) {
return flag ? total + 10 : total;
}
}
})();
console.log(car.price(5)); // 23
console.log(car.yd(true)); // 33
5.5 案例:關(guān)于閉包的思考題
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
return function() {
return this.name;
};
}
};
console.log(object.getNameFunc()())
-----------------------------------------------------------------------------------
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
var that = this;
return function() {
return that.name;
};
}
};
console.log(object.getNameFunc()())
6. 遞歸
6.1 什么是遞歸
遞歸:如果一個函數(shù)在內(nèi)部可以調(diào)用其本身,那么這個函數(shù)就是遞歸函數(shù)。簡單理解:函數(shù)內(nèi)部自己調(diào)用自己, 這個函數(shù)就是遞歸函數(shù)。
注意:遞歸函數(shù)的作用和循環(huán)效果一樣,由于遞歸很容易發(fā)生“棧溢出”錯誤(stack overflow),所以必須要加退出條件return。
6.2 利用遞歸求1~n的階乘
//利用遞歸函數(shù)求1~n的階乘 1 * 2 * 3 * 4 * ..n
function fn(n) {
if (n == 1) { //結(jié)束條件
return 1;
}
return n * fn(n - 1);
}
console.log(fn(3)); // 6
6.3 利用遞歸求斐波那契數(shù)列
// 利用遞歸函數(shù)求斐波那契數(shù)列(兔子序列) 1、1、2、3、5、8、13、21...
// 用戶輸入一個數(shù)字 n 就可以求出 這個數(shù)字對應(yīng)的兔子序列值
// 我們只需要知道用戶輸入的n 的前面兩項(n-1 n-2)就可以計算出n 對應(yīng)的序列值
function fb(n) {
if (n === 1 || n === 2) {
return 1;
}
return fb(n - 1) + fb(n - 2);
}
console.log(fb(3));
6.4 利用遞歸遍歷數(shù)據(jù)(多維數(shù)組)
var data = [{
id: 1,
name: '家電',
goods: [{
id: 11,
gname: '冰箱',
goods: [{
id: 111,
gname: '海爾'
}, {
id: 112,
gname: '美的'
},]
}, {
id: 12,
gname: '洗衣機'
}]
}, {
id: 2,
name: '服飾'
}];
// 當(dāng)我們輸入想要輸入的id好,就可以返回對應(yīng)的數(shù)據(jù)對象
function getId(data, id) {
var object = {};
// 使用forEach遍歷數(shù)組
data.forEach(function (item) {
if (item.id === id) {
object = item;
return item;
/**
* 使用遞歸遍歷多維數(shù)組
*/
} else if (item.goods && item.goods.length > 0) {
object = getId(item.goods, id);
}
});
return object;
}
console.log(getId(data, 11));
console.log(getId(data, 1));
console.log(getId(data, 111));
console.log(getId(data, 666));
7. 淺拷貝
- 淺拷貝只是拷貝一層,更深層次對象級別的只拷貝引用。 拷貝對象的引用會導(dǎo)致修改拷貝的對象導(dǎo)致原來的對象也被修改。
// 1. 淺拷貝只是拷貝一層,更深層次對象級別的只拷貝引用
// 2. 深拷貝拷貝的是多層,每一級的數(shù)據(jù)都會拷貝
let obj = {
id: '1',
name: 'andy',
msg: {
age: 18
}
};
// 淺拷貝遇到對象級別的只會拷貝對象的引用
let target = {};
// es6 中實現(xiàn)淺拷貝的語法糖
Object.assign(target, obj);
// 修改對象級別的屬性
target.msg.age = 100;
// 打印拷貝之后的對象
console.log(target);
// 打印源對象
console.log(obj);
- 運行結(jié)果:

8. 深拷貝
- 深拷貝拷貝的是多層,每一級的數(shù)據(jù)都會拷貝。
let obj = {
id: '1',
name: '張三',
msg: {
title: '今天真開心',
content: '666',
format: {
date: '2020',
birthday: '1998'
}
},
size: ['big', 'small', 'mini']
};
let target = {};
/**
* 實現(xiàn)對對象的深拷貝
* @param targetObj
* @param oldObj
*/
function deepCopy(targetObj, oldObj) {
// 1. 循環(huán)遍歷對象
for (let key in oldObj) {
// 判斷我們的屬性屬于哪種數(shù)據(jù)類型
// 1. 獲取屬性值oldObj[key]
let item = oldObj[key];
if (item instanceof Array) {
// 如果屬性是一個數(shù)組
targetObj[key] = [];
// 遞歸進(jìn)行拷貝
deepCopy(targetObj[key], item);
} else if (item instanceof Object) {
// 3. 判斷當(dāng)前的屬性是否為一個對象 Object
targetObj[key] = {};
deepCopy(targetObj[key], item);
} else {
// 如果屬性是一個普通的屬性
targetObj[key] = item;
}
}
}
deepCopy(target, obj);
console.log(target);
// 修改賦值之后的對象
target.msg.title = '李四';
console.log(target); // 原來對象的屬性被修改了
console.log(obj); // 拷貝的對象的屬性未被修改
