JavaScript是一种基于对象和时间驱动的简单描述性语言。从服务器端被下载到客户端,由浏览器执行。
特点:
一般用来编写客户端的脚本。
主要是用来在 HTML 页面中添加交互行为。
是一种解释性语言,边解释边执行。
蓝桥杯js复习
JavaScript是一种基于对象和时间驱动的简单描述性语言。从服务器端被下载到客户端,由浏览器执行。
特点:
一般用来编写客户端的脚本。
主要是用来在 HTML 页面中添加交互行为。
是一种解释性语言,边解释边执行。
变量
在JavaScript中,使用var声明变量
语法格式:
var 变量名 = 值;
变量命名原则:
1、变量由字母、下划线、$或数字组成,并且必须由字母、下划线、$开头
2、变量名不能命名为系统关键字和保留字
数据类型
在js中数据类型分为两种:
基本数据类型:字符串(string)、数字(number)、布尔(boolean)、空(null)、未定义(undefined);采用值传递的传参方式、栈存储的储存方式
引用数据类型:对象(object);采用地址传递的传参方式、堆存储的存储方式
查看数据类型使用typeof关键字
判断数据类型
typeof
判断一个数据的类型,用得最多的就是 typeof 操作符, 但是使用 typeof 常常会遇到以下问题:
无法判断 null。
无法判断除了 function 之外的引用类型。
instanceof
typeof 无法精确地判断引用类型,这时,可以使用 instanceof 运算符但是 instanceof 运算符一定要是判断对象实例的时候才是正确的,也就是说,它不能判断原始类型,如:
const str1 = "qwe";
const str2 = new String("qwe");
console.log(str1 instanceof String); // false,无法判断原始类型。
console.log(str2 instanceof String); // true
Object.prototype.toString.call(xxx)
Object.prototype.toString :实际项目中要封装判断类型的工具函数一般都是用的它。
调用 Object.prototype.toString 方法,会统一返回格式为 [object Xxx] 的字符串,用来表示该对象(原始类型是包装对象)的类型。需要注意的是,在调用该方法时,需要加上 call 方法
call 是函数的方法,是用来改变 this 指向的,用 apply 方法也可以。
如果不改变 this 指向为我们的目标变量 xxx,this 将永远指向调用的 Object.prototype,也就是原型对象,对原型对象调用 toString 方法,结果永远都是 [object Object]
运算符与表达式
算数运算符
在 JavaScript 中有七种算术运算符:加 +、减 -、乘 *、除 /、取余 %、自加 ++、自减 --。
比较运算符
比较运算符是比较操作数,并根据表达式判断为真或为假,来返回一个布尔类型的值。
在 JavaScript 中有如下表所示的比较运算符。
运算符 | 描述 |
等于(==) | 当等于号左右两边的操作数相等时,返回 true。 |
不等于(!=) | 当不等号左右两边的操作数不相等时,返回 true。 |
全等(===) | 当全等号左右两边的操作数相等且类型相同时,返回 true。 |
不全等(!==) | 当不全等号左右两边的操作数不相等或者类型不相同时,返回 true |
大于(>) | 当大于号左边的操作数大于右边的操作数时,返回 true。 |
大于等于(>=) | 当大于等于号左边的操作数大于或者等于右边的操作数时,返回 true。 |
小于(<) | 当小于号左边的操作数小于右边的操作数时,返回 true。 |
小于等于(<=) | 当小于等于号左边的操作数小于或者等于右边的操作数时,返回 true。 |
赋值运算符
在 JavaScript 中,有五种赋值运算符:=、+=、-=、*=、/=。
逻辑运算符
在 JavaScript 中,逻辑运算符包括:与 &&、或 ||、非 !。
条件运算符
条件运算符也可叫三目运算符,你可以用来判断条件的真假,根据真假执行不同的语句。
条件运算符的使用格式如下:
条件表达式 ? 表达式1 : 表达式2
分支与循环
分支结构
在 JavaScript 中分支结构有两种:
if 语句。
switch 语句。
循环结构
while 语句(先判断再执行)
do...while 语句(先执行再判断)
for 语句
for...in 语句
for...in 语句循环一个指定的变量来循环指定对象的所有可枚举属性。
语法格式:
for (i in obj){
}
返回值为数组元素下标
函数
在 JavaScript 中有两种函数,一种是有返回值的函数,一种是没有返回值的函数。
定义函数的格式为:
function 函数名(参数){
...
return 返回值;
}
参数和返回值可有可无。
变量的作用域
作用域是变量与函数的可访问范围,在 JavaScript 中,变量的作用域有全局作用域和局部作用域两种。
局部作用域
在函数内声明的变量为局部变量,作用域只在函数内部有效,也只能被函数内部访问。
函数开始执行时创建局部变量,函数执行完成后局部变量自动销毁
全局作用域
在函数外部声明的变量,即为全局变量。作用域针对整个全局,网页的所有脚本和函数都可以访问该变量
内置对象
数学对象
JavaScript 中的数学对象为 Math,它的内部有一些数学的属性和函数方法。
Math 的常用属性如下表所示:
属性 | 描述 |
Math.E | 自然对数的底数 |
Math.LN2 | 2 的自然对数 |
Math.PI | 圆周率 |
Math.SQRT2 | 2 的平方根 |
Math 的常用方法如下表所示:
属性 | 描述 |
Math.abs(x) | 返回一个数的绝对值。 |
Math.pow(x, y) | 返回一个数的 y 次幂。 |
Math.random() | 返回一个 0 到 1 之间的伪随机数。 |
Math.sqrt(x) | 返回一个数的平方根。 |
Math.round() | 返回四舍五入后的整数。 |
Math.exp(x) | 返回欧拉常数的参数次方。 |
日期对象
在 JavaScript 中,日期对象是 Date,用于处理日期和时间。
其常用方法如下所示:
方法 | 描述 |
getDate() | 返回一个月的某一天。 |
getDay() | 返回一周中的某一天。 |
getFullYear() | 返回年份。 |
getHours() | 返回小时。 |
getMonth() | 返回月份。 |
getTime() | 返回毫秒数。 |
setFullYear() | 设置年份。 |
setDate() | 设置一个月中的某一天。 |
setMonth() | 设置月份。 |
数组对象
在 JavaScript 中,数组对象是 Array,在其语法格式为:
var 数组名 = new Array(元素1, 元素2,...,元素n);//在定义数组时,不一定要写入元素,可以只定义一个空数组
创建数组我们还可以简写为:
var 数组名 = [元素1, 元素2,...,元素n];
内置对象
slice() 是用来做数组切片操作的,也就是取数组中的部分值,左闭右开。例如:
arr.slice(2, 4);
unshift() 可以在数组的头部增加新的元素。
数组名.unshift(待添加项);
sort() 可以给数组中的元素从小到大进行排序。
数组名.sort();
升序排列:
数组名.sort((a,b)=>a-b);
降序排列:
数组名.sort((a,b)=>b-a);
reverse() 可以将数组中的元素进行逆序排列。
数组名.reverse();
join() 可以将数组中的字符拼接成字符串。
数值名.join();
我们可以使用 length 来获取数组的长度。
数组名.length;
concat() 可以将两个数组拼接在一起。
// 将 数组2 拼接到 数组1 里
数组1.concat(数组2);
includes() 可以用来判断该数组中是否包含某个元素,返回一个布尔值
数组.includes(元素);
toString() 可以将数组中的值转换成字符串类型。
数组名.toString();
indexOf() 可以用来查找指定元素的下标值。
arr.indexOf(元素);
字符串对象
获取字符串的长度与获取数组的长度是一样的,都是使用 length。
字符串.length;
toLowerCase() 可以把字符串的大写字母转换成小写字母。
字符串.toLowerCase();
toUpperCase() 可以把字符串中的小写字母转换成大写字母。
字符串.toUpperCase();
charAt() 是用于根据指定下标从一个字符串中返回指定的字符。
字符串.charAt(下标值);
substring() 可以通过下标来选取字符串中的部分字符
字符串.substring();
replace() 可以用来替换指定字符串的内容。
字符串.replace(待替换的字符串, 新的字符串);
split 可以使用指定的分隔符将一个字符串分割成子字符串数组。
字符串.split();
indexOf() 是寻找某个字符在字符串中首次出现的位置。
字符串.indexOf(字符);
DOM和BOM
DOM
DOM 的英文全称为 Document Object Model(文档对象模型),它是浏览器为每个窗口内的 HTML 页面创建的一个 document 对象来对页面的元素进行操作。
常用的 DOM 属性如下表所示:
属性 | 描 述 |
document.title | 获取文档的 title 元素。 |
document.body | 获取文档的 body 元素。 |
document.URL | 获取文档的 URL。 |
document.forms | 获取文档的 form 元素。 |
document.images | 获取文档的 img 元素。 |
document.links | 获取文档的 a 元素。 |
document.cookie | 获取文档的 cookie。 |
document.referrer | 返回来用户当前浏览页面的 URL。 |
常用的 DOM 方法如下表所示:
方法 | 描 述 |
document.getElementById() | 通过 id 获取元素。 |
document.getElementByTagName() | 通过标签名获取元素。 |
document.getElementByClassName() | 通过 class 获取元素。 |
document.getElementByName() | 通过 name 获取元素。 |
document.querySelector() | 通过选择器获取第一个元素。 |
document.querySelectorAll() | 通过选择器获取所有元素。 |
document.createElement() | 创建元素节点。 |
document.createTextNode() | 创建文本节点。 |
document.write() | 输出内容。 |
document.writeln() | 输出内容并换行。 |
BOM
BOM(Browser Object Mode)是浏览器对象模型,通过操作对象的属性和方法来实现与浏览器的交互。
BOM 的构成如下所示:

window是顶级对象,window之下有:
document是DOM对象
location是用于获取或设置窗体
navigation包含浏览器配置相关的信息
history是浏览器的历史记录
screen用于显示设备信息
window 对象的方法如下所示:
方法 | 描 述 |
alert() | 显示一个警告框。 |
prompt() | 显示可提示用户输入的对话框。 |
confirm() | 显示一个有确认和取消按钮的对话框。 |
open() | 打开一个新的浏览器窗口。 |
close() | 关闭浏览器。 |
print() | 打印当前窗口内容。 |
事件
鼠标事件
鼠标事件是当用鼠标对页面进行一些操作时会触发的事件。
常用的鼠标事件如下表所示:
事件 | 说明 |
onclick | 鼠标点击事件 |
onmouseover | 鼠标移入事件 |
onmouseout | 鼠标移出事件 |
onmousedown | 鼠标按下事件 |
onmouseup | 鼠标松开事件 |
onmousemove | 鼠标移动事件 |
实例代码示范:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
button {
width: 100px;
}
</style>
<script>
window.onload = function () {
var btn1 = document.getElementById("btn1");
btn1.onmousedown = function () {
// 鼠标按下
btn1.style.background = "#ffefa1";
};
btn1.onmouseup = function () {
// 鼠标松开
btn1.style.color = "#51c2d5";
btn1.style.background = "#f4f9f9";
};
};
</script>
</head>
<body>
<button id="btn1">1</button>
</body>
</html>
键盘事件
常用的键盘事件只有以下两个:
onkeydown:键盘按下会触发的事件。
onkeyup:键盘松开会触发的事件。
表单事件
在 JavaScript 中,常用表单事件如下表所示:
事件 | 说明 |
onfocus | 表单元素聚焦时触发。 |
onblur | 表单元素失焦时触发。 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>onfocus 与 onblur</title>
<script>
window.onload = function () {
var name = document.getElementById("username");
name.onfocus = function () {
// 当聚焦到该输入框时,把输入框的内容置为空,并设置字体颜色为蓝色
if (name.value == "输入你的名字") {
name.value = "";
}
this.style.color = "#77acf1";
};
name.onblur = function () {
// 当失去焦点时,显示输入框的默认内容
if (name.value == "") {
name.value = "输入你的名字";
}
};
};
</script>
</head>
<body>
姓名:<input type="text" id="username" value="输入你的名字" />
</body>
</html>
event对象
event 对象,是事件对象,通过事件对象的属性,我们可以了解一个事件的详细情况。
常用的 event 对象属性如下表所示:
属性 | 说明 |
type | 查看事件类型 |
查看键值码 | |
shiftKey | 是否按下 shift 键 |
ctrlkey | 是否按下 ctrl 键 |
altkey | 是否按下 alt 键 |
实例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>keyCode 的使用</title>
<style></style>
<script>
window.onload = function () {
var value = document.getElementById("item");
var value1 = document.getElementById("item1");
value.onkeydown = function (e) {
// 判断键入的是否为 shift、alt、ctrl
if (e.shiftKey || e.altKey || e.ctrlKey) {
value1.innerHTML = "不能键入 shift|alt|ctrl";
value1.style.color = "red";
// 当键入的不为 shift、alt、ctrl 时,输出你的键入码
} else {
value1.innerHTML = "你输入的键值码为:" + e.keyCode;
value1.style.color = "black";
}
};
};
</script>
</head>
<body>
<input type="text" id="item" />
<p id="item1"></p>
</body>
</html>
DOM 0级事件与 DOM 2级事件
DOM 0级事件的使用
DOM 0 级事件是直接使用像 onclick 这样的方式来实现相应的事件,例如,你可以在 HTML 标签里直接写 onclick 事件或者在 JavaScript 中直接使用 onclick = function(){}。
注意:在DOM0级事件中,如果有多个事件,后面的事件会覆盖前面的事件
DOM 2级事件的使用
DOM2级事件可以让多个事件执行。
在DOM2级事件里,所有DOM节点都有两个方法,分别是:addEvenetListener和removeEventListener
语法格式:
target.addEvenetListener(type,listener[,useCapture]);// 添加事件
target.removeEventListener(type,listener[,useCapture]);// 移出事件
其参数说明如下所示:
type 是事件类型。
listener 是事件处理的方法。
useCapture 指定事件是否在捕获或冒泡阶段执行,其值为布尔类型,当取值为 true 时,事件句柄在捕获阶段执行,当取值为 false 时,事件句柄在冒泡阶段执行。
示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input id="btn" type="button" value="按钮" />
<script>
var btn = document.getElementById("btn");
btn.addEventListener("click", handler, false);
function handler() {
alert("已点击");
document.getElementById("btn").removeEventListener("click", handler, false);
}
</script>
</body>
</html>
原生AJAX
AJAX 的英文全称为 Asynchronous JavaScript And XML,Asynchronous 是异步的意思。何为异步呢?在这里异步是指通过 AJAX 向服务器请求数据,在不刷新整个页面的情况下,更新页面上的部分内容。

用户在浏览器执行一些操作,通过 AJAX 引擎发送 HTTP 请求到服务器请求数据,请求成功后,由服务器把请求的数据拿给 AJAX 引擎,再由 AJAX 拿给浏览器。
浏览器如果直接向服务器请求数据的话,在请求过程中,你是不能对页面进行其他操作的,这叫同步请求,而把请求数据这个活外包给 AJAX 后,在请求过程中,用户还是可以对页面进行其他操作,这就是我们的异步请求了,也是 AJAX 的核心特点。
创建AJAX的基本步骤
1、创建XMLHttpRequest对象
在AJAX中,HMLHttpRequest对象是用来与服务器进行数据交换的。
var httpRequest = new XMLHttpRequest();
为了保证浏览器的兼容性,我们可以用以下方式创建:
if (window.XMLHttpRequest) {
// Mozilla,Safari,IE7+ 等浏览器适用
httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) {
// IE 6 或者更老的浏览器适用
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
2、向服务器发送请求
在步骤一中我们已经创建了用于服务器交换数据的 XMLHttpRequest 对象,要向服务器发送请求,我们需要调用该对象中的 open 和 send 方法。
使用:
// 规定发送请求的一些要求
httpRequest.open("method", "url", async);
// 将请求发送到服务器
httpRequest.send();
open 方法中的参数说明如下:
method 是请求的类型,常见的有 GET 和 POST。
url 是请求的地址。
async 是设置同步或者异步请求,其值为布尔类型,当为 true 时,使用异步请求;当为 false 时,使用同步请求,默认为 true。
3、服务器响应状态
我们使用 HTTP 请求数据后,请求是否成功,会反馈给我们相应的请求状态。我们使用 onreadystatechange 去检查响应的状态,当 httpRequest.readyState 为 4 并且 httpRequest.status 等于 200 时,说明数据请求成功,其使用如下:
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState == 4 && httpRequest.status == 200) {
// 请求成功执行的代码
} else {
// 请求失败执行的代码
}
};
示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AJAX 的使用</title>
<script>
window.onload = function () {
if (window.XMLHttpRequest) {
// Mozilla,Safari,IE7+ 等浏览器适用
var httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) {
// IE 6 或者更老的浏览器适用
var httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
// 规定发送请求的一些要求
httpRequest.open(
"GET",
"https://jsonplaceholder.typicode.com/users",
true
);
// 将请求发送到服务器
httpRequest.send();
httpRequest.onreadystatechange = function () {
console.log(httpRequest.readyState);
console.log(httpRequest.status);
if (httpRequest.readyState == 4 && httpRequest.status == 200) {
// 请求成功执行的代码
document.getElementById("item").innerHTML = "请求成功";
} else {
// 请求失败执行的代码
document.getElementById("item").innerHTML = "请求失败";
}
};
};
</script>
</head>
<body>
<div id="item"></div>
</body>
</html>
readyState 的值,它的取值有以下几种:
0 代表未初始化请求。
1 代表已与服务器建立连接。
2 代表请求被接受。
3 代表请求中。
4 代表请求完成。
正则表达式
正则表达式是用来验证字符串的模版,开发中为了验证邮箱、手机号、身份证号等字符串是否符合要求,通常会使用正则表达式。
作为匹配字符串的模版,正则表达式本身也是字符串,例如 aaa 就是一个最简单的正则表达式,只不过该表达式只能匹配字符串 aaa,如果一个正则表达式只能匹配 aaa 那么它作为正则表达式的意义就没多大了。真正的正则表达式是能够匹配多个字符串的通用的字符串。
如何使用正则表达式
1、编写一个正则表达式
var regularExpression = /正则表达式/;
2、使用正则表达式验证目标字符串
var flag = regularExpression.test("目标字符串");
上述代码调用了正则表达式的 test 方法进行验证,调用时传入一个需要验证的目标字符串,验证通过返回 true ,不通过返回 false。
如何编写一个正则表达式
常用的正则表达式:
常用表达式:
表达式 | 描述 |
[a-z] | 查找任何从小写 a 到小写 z 的字符 |
[A-Z] | 查找任何从大写 A 到大写 Z 的字符 |
[0-9] | 查找任何从 0 至 9 的数字 |
[abc] | 查找括号内的任意一个字符 |
[^abc] | 查找除了括号内的任意字符 |
常用的元字符(特殊字符)
字符 | 描述 |
\w | 匹配数字、字母、下划线 |
\W | 匹配非数字、字母、下划线 |
\d | 匹配数字 |
\D | 匹配非数字 |
\s | 匹配空白字符(空格、换行) |
\S | 匹配非空白字符 |
\n | 匹配换行符 |
常用的限定符
字符 | 描述 |
* | 匹配前面的子表达式零次或多次 |
+ | 匹配前面的子表达式一次或多次 |
? | 匹配前面的子表达式零次或一次 |
{n} | 匹配确定的n次 |
{n,} | 至少匹配n次 |
{n,m} | 最少匹配n次且最多匹配m次 |
常用的修饰符
修饰符 | 描述 |
i | 执行对大小写不敏感的匹配 |
g | 执行全局匹配(查找所有匹配而非在找第一个匹配后停止) |
m | 执行多行匹配 |
其他
修饰符 | 描述 |
^ | 以...开始 |
$ | 以...结束 |
示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
<script>
window.onload = function () {
var userForm = document.getElementById("userForm");
var usernameText = document.getElementById("username");
var msg = document.getElementById("msg");
btn1.onclick = function () {
var regExp = /^[a-zA-Z]{6,10}$/;
if (!regExp.test(username.value)) {
msg.innerHTML = '<b style="color:red;">用户名格式不正确</b>';
} else {
// 提交表单
userForm.submit();
}
};
};
</script>
</head>
<body>
<div id="msg"></div>
<form id="userForm" action="handle.html" method="get">
用户名: <input type="text" id="username" />
<input id="btn1" type="button" value="提交" />
</form>
</body>
</html>
题目
挑战介绍
本节我们来挑战一道大厂面试真题 —— 实现类型判断。
挑战准备
新建一个 getType.js 文件,在文件里写一个名为 getType 的函数,并导出这个函数,如下图所示:

这个文件在环境初始化时会自动生成,如果发现没有自动生成就按照上述图片自己创建文件和函数,函数代码如下:
function getType(target) {
// 补充代码
}
module.exports = getType;
挑战内容
请封装一个函数,能够以字符串的形式精准地返回数据类型。
要求返回的类型全部由小写字母组成。
示例:
输入 | 输出 |
true | 'boolean' |
100 | 'number' |
'abc' | 'string' |
100n | 'bigint' |
null | 'null' |
undefined | 'undefined' |
Symbol('a') | 'symbol' |
[] | 'array' |
{} | 'object' |
function fn() {} | 'function' |
new Date() | 'date' |
/abc/ | 'regexp' |
new Error() | 'error' |
new Map() | 'map' |
new Set() | 'set' |
new WeakMap() | 'weakmap' |
new WeakSet() | 'weakset' |
注意事项
文件名、函数名不可随意更改。
文件中编写的函数需要导出,否则将无法提交通过。
答案代码:
function getType(target) {
const type = typeof target;
if (type !== "object") {
return type;
}
return Object.prototype.toString
.call(target)
.replace(/^\[object (\S+)\]$/, "$1")
.toLocaleLowerCase();
}
类型判断类:
1、JavaScript有哪些数据类型?
JavaScript中的数据类型分为原始类型和对象类型。
原始类型有:number, string, null, boolean, bigint, undefined, symbol
对象类型也称为引用类型
2、typeof null的结果是什么?
结果为object,因为JavaScript的不同对象在底层都表示为二进制,而JavaScript会把二进制前三位为0的判断为object类,null在底层的二进制表示全0,因此也会被判断为object
3、原始类型和引用类型的区别是什么?
类型 | 原始类型 | 对象类型 |
值 | 不可改变 | 可以改变 |
属性和方法 | 不能添加 | 可以添加 |
存储值 | 值 | 地址 |
比较 | 值的比较 | 地址的比较 |
4、typeof 和instaceof的区别是什么?
typeof用来判断数据类型
instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上,也可以用来判断数据的类型
typeof返回一个变量的类型字符串,instanceof返回的是一个布尔值
typeof可以判断除了null以外的基础数据类型,但判断引用类型时,除了function类型,其他类型无法准确判断
instanceof可以准确地判断各种引用类型,但是不能正确判断原始数据类型
5、Symbol解决了什么问题?
答:Symbol 是 ES6 时新增的特性,Symbol 是一个基本的数据类型,表示独一无二的值,主要用来防止对象属性名冲突问题。
判断对象是否为空
挑战介绍
本节我们来挑战一道大厂面试真题 —— 判断对象是否为空。
挑战准备
新建一个 isEmptyObject.js 文件,在文件里写一个名为 isEmptyObject 的函数,并导出这个函数,如下图所示:

这个文件在环境初始化时会自动生成,如果发现没有自动生成就按照上述图片自己创建文件和函数,函数代码如下:
function isEmptyObject(obj) {
// 补充代码
}
module.exports = isEmptyObject;
挑战内容
实现一个函数,判断传入的对象是否为空。如果对象为空返回 true,否则返回 false。
不必考虑传入原始类型的情况,本题测试用例中传入的参数都是对象类型。
示例:
输入:{}
输出:true
输入:{ name: 'lin' }
输出:false
注意事项
文件名、函数名不可随意更改。
文件中编写的函数需要导出,否则将无法提交通过。
答案代码:
function isEmptyObject(obj) {
for (let o in obj) {
return false;
}
return true;
}
function isEmptyObject(obj) {
return Object.keys(obj).length === 0;
}
function isEmptyObject(obj) {
return JSON.stringify(obj) === "{}";
}
知识讲解this
this 是当前执行上下文(global、function 或 eval)的一个属性。
也就是说,我们可以把 JS 中的 this 分为三种,分别是:
全局上下文中的 this。
函数上下文中的 this。
eval 上下文中的 this。
node 环境中的 this 和 web 环境中的 this 是不同的
全局上下文的this
全局上下文的 this 指向非常明确,不管有没有启用严格模式,都指向 window 对象。
给 this 添加属性,就相当于给 window 添加属性,给 window 添加属性,就相当于给 this 添加属性,如下代码所示:
this.userName = "zhangsan";
window.age = 18;
console.log(this.age); // 输出 18
console.log(window.userName); // 输出 'zhangsan'
函数上下文的this
函数上下文中的this与arguments一样,就是函数的隐式参数,可以在任意函数中调用,它的指向不是固定不变的,取决于函数处于什么位置、以什么方式调用。

全局上下文中的函数
直接调用全局上下文中的函数,this 指向默认情况下为 window。
function fn() {
console.log(this); // 输出 window
}
fn();
function fn() {
let a = 1;
console.log(this.a); // 输出 2
}
var a = 2;
fn();
但是在严格模式下,this 指向的就是 undefined。
function fn() {
"use strict";
console.log(this); // 输出 undefined
}
fn();
对象中的函数
调用对象中的函数,this 指向为这个对象。
const obj = {
a: 1,
fn() {
console.log("this :>> ", this); // 输出 obj
console.log("this.a :>> ", this.a); // 输出 1
},
};
obj.fn();
但是如果函数嵌套有函数,此时的 this 指向为 window,就非常令人迷惑。
const obj = {
a: 1,
fn() {
return function () {
console.log("this :>> ", this); // 输出 window
console.log("this.a :>> ", this.a); // 输出 100
};
},
};
var a = 100;
obj.fn()();
其实可以这么理解:
obj.fn()();
等价于;
const temp = obj.fn(); // 定义一个临时变量来存储 obj.fn 返回的函数
temp(); // 执行这个函数
上面代码示例中的 temp 在运行时是处在 window 环境中的,所以 this 指向 window。
箭头函数
遇到对象里有函数嵌套函数的情况,想要 this 指向这个对象,es6 之前,可以用一个临时变量 _this 来暂存 this,代码如下:
const obj = {
a: 1,
fn() {
const _this = this;
return function () {
console.log("this :>> ", _this); // 输出 obj
console.log("this.a :>> ", _this.a); // 输出 1
};
},
};
obj.fn()();
es6 推出了箭头函数的概念,之后我们就可以使用箭头函数解决这个问题,代码如下:
const obj = {
a: 1,
fn() {
return () => {
console.log("this :>> ", this); // 输出 obj
console.log("this.a :>> ", this.a); // 输出 1
};
},
};
obj.fn()();
对于普通函数来说,内部的 this 指向函数运行时所在的对象。
对于箭头函数,它不会创建自己的 this,它只会从自己的作用域链的上一层继承 this。
所以这里 fn 中嵌套的匿名箭头函数中的 this,指向它作用域链的上一层的 this,也就是函数 fn 的 this,也就是 obj。
构造函数
构造函数内,如果返回值是一个对象,this 指向这个对象,否则 this 指向新建的实例。
function Person(name) {
this.name = name;
}
const p = new Person("zhangsan");
console.log(p.name); // 'zhangsan'
function Person(name) {
this.name = name;
return {
name: "xxx",
};
}
const p = new Person("zhangsan");
console.log(p.name); // 'xxx'
function Person(name) {
this.name = name;
return {};
}
const p = new Person("zhangsan");
console.log(p.name); // 'undefined'
如果有返回值,但是不是一个对象,this 还是指向实例。
function Person(name) {
this.name = name;
return 123;
}
const p = new Person("zhangsan");
console.log(p.name); // 'zhangsan'
显式改变函数上下文的this
了解了函数中的 this 指向后,我们可以使用 call、apply 和 bind 来显式改变函数中的 this 指向。
call
Function.prototype.call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
function fn() {
console.log(this.name);
}
const obj = {
name: "zhangsan",
};
fn.call(obj); // 指定 this 为 obj,输出 'zhangsan'
apply
Function.prototype.apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数。
apply 和 call 的功能完全一样,只是传参形式不一样,call 是传多个参数,apply 是只传参数集合。
function add(x, y, z) {
return this.x + this.y + this.z;
}
const obj = {
x: 1,
y: 2,
z: 3,
};
console.log(add.call(obj, 1, 2, 3)); // 输出 6
console.log(add.apply(obj, [1, 2, 3])); // 输出 6,只是传参形式不同而已
bind
Function.prototype.bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
bind 和 call、apply 的区别是,函数调用 call 和 apply 会直接调用,而调用 bind 是创建一个新的函数,必须手动去再调用一次,才会生效。
function add(x, y, z) {
return this.x + this.y + this.z;
}
const obj = {
x: 1,
y: 2,
z: 3,
};
const add1 = add.bind(obj, 1, 2, 3); // bind 会返回一个新的函数
console.log(add1()); // 执行新的函数,输出 6
实现 call 函数
挑战介绍
本节我们来挑战一道大厂面试真题 —— 实现 call 函数。
挑战准备
新建一个 myCall.js 文件,在文件里写一个名为 myCall 的函数,并导出这个函数,如下图所示:

这个文件在环境初始化时会自动生成,如果发现没有自动生成就按照上述图片自己创建文件和函数,函数代码如下:
function myCall(fn, context) {
// 补充代码
}
module.exports = myCall;
挑战内容
请封装一个 myCall 函数,用来实现 call 函数的功能。
myCall 函数接收多个参数,第一个参数是 fn,代表要执行的函数,第二个参数是 context,代表需要显式改变的 this 指向,之后的参数都是 fn 的参数。
示例:
const person = {
userName: "zhangsan",
};
function fn() {
return this.userName;
}
myCall(fn, person); // 执行函数 fn,返回 'zhangsan'
// fn 传参数的情况
const obj = {
count: 10,
};
function fn(x, y, z) {
return this.count + x + y + z;
}
myCall(fn, obj, 1, 2, 3); // 执行函数 fn,返回 16
注意事项
文件名、函数名不可随意更改。
文件中编写的函数需要导出,否则将无法提交通过。
题解:
// 要处理 context 不传值的情况,传一个默认值 window。
function myCall(fn, context = window) {
context.fn = fn;
// 处理函数 fn 的参数,执行 fn 函数时把参数携带进去。
const args = [...arguments].slice(2);
// 获取执行函数 fn 产生的返回值。
const res = context.fn(...args);
delete context.fn;
// 最终返回这个返回值
return res;
}
知识延伸
处理边缘情况
上文代码中实现的call()函数中,有一些边缘情况可能会导致报错,例如把要指向的对象指向一个原始值。
这时,就需要参考一下原生的 call 函数是如何解决的这个问题,我们打印出来看一下:
var userName = "xxx";
const person = {
userName: "zhangsan",
};
function fn(type) {
console.log(type, "->", this.userName);
}
fn.call(0, "number");
fn.call(1n, "bigint");
fn.call(false, "boolean");
fn.call("123", "string");
fn.call(undefined, "undefined");
fn.call(null, "null");
const a = Symbol("a");
fn.call(a, "symbol");
fn.call([], "引用类型");
可以看到,undefined 和 null 指向了 window,原始类型和引用类型都是 undefined。
其实是因为,原始类型指向对应的包装类型,引用类型就指向这个引用类型,之所以输出值都是 undefined,是因为这些对象上都没有 userName 属性。
改造一下我们的 myCall 函数,实现原始类型的兼容,代码如下:
Function.prototype.myCall = function (context = window) {
if (context === null || context === undefined) {
context = window; // undefined 和 null 指向 window
} else {
context = Object(context); // 原始类型就包装一下
}
context.fn = this;
const args = [...arguments].slice(1);
const res = context.fn(...args);
delete context.fn;
return res;
};
还有另外一种边缘情况,假设对象上本来就有一个 fn 属性,执行下面的调用,对象上的 fn 属性会被删除,代码如下:
const person = {
userName: "zhangsan",
fn: 123,
};
function fn() {
console.log(this.userName);
}
fn.myCall(person);
console.log(person.fn); // 输出 undefined,本来应该输出 123
因为对象上本来的 fn 属性和 myCall 函数内部临时定义的 fn 属性重名了。
继续改造 myCall 函数,代码实现如下:
Function.prototype.myCall = function (context = window) {
if (context === null || context === undefined) {
context = window;
} else {
context = Object(context);
}
const fn = Symbol("fn"); // 用 symbol 处理一下
context[fn] = this;
const args = [...arguments].slice(1);
const res = context[fn](...args);
delete context[fn];
return res;
};
至此,一个功能尽可能完善的 myCall 函数,终于写完了。
call的使用场景
call 的使用场景非常多,所有调用 call 的使用场景都是为了显式地改变 this 的指向,能用 call 解决的问题也能用 apply 解决,因为它们俩只是传参形式不同。下面一起来看 call 常用的四个使用场景。
1、精准判断一个数据类型
精准地判断一个数据的类型,可以用到Object.prototype.toString.call(xxx)
调用该方法返回[object Xxx]的字符串
// 引用类型
console.log(Object.prototype.toString.call({})); // '[object Object]'
console.log(Object.prototype.toString.call(function () {})); // "[object Function]'
console.log(Object.prototype.toString.call(/123/g)); // '[object RegExp]'
console.log(Object.prototype.toString.call(new Date())); // '[object Date]'
console.log(Object.prototype.toString.call(new Error())); // '[object Error]'
console.log(Object.prototype.toString.call([])); // '[object Array]'
console.log(Object.prototype.toString.call(new Map())); // '[object Map]'
console.log(Object.prototype.toString.call(new Set())); // '[object Set]'
console.log(Object.prototype.toString.call(new WeakMap())); // '[object WeakMap]'
console.log(Object.prototype.toString.call(new WeakSet())); // '[object WeakSet]'
// 原始类型
console.log(Object.prototype.toString.call(1)); // '[object Number]'
console.log(Object.prototype.toString.call("abc")); // '[object String]'
console.log(Object.prototype.toString.call(true)); // '[object Boolean]'
console.log(Object.prototype.toString.call(1n)); // '[object BigInt]'
console.log(Object.prototype.toString.call(null)); // '[object Null]'
console.log(Object.prototype.toString.call(undefined)); // '[object Undefined]'
console.log(Object.prototype.toString.call(Symbol("a"))); // '[object Symbol]'
这里调用call是为了显式地改变this指向,指向目标变量,如果不改变this的指向为目标变量,this将永远指向调用的Object.prototype原型对象。原型对象调用toString方法,结果是[object Object]
2、伪数组转数组
伪数组转数组,在es6之前可以使用Array.prototype.slice.call(xxx)
function add() {
const args = Array.prototype.slice.call(arguments);
// 也可以这么写 const args = [].slice.call(arguments)
args.push(1); // 可以使用数组上的方法了
}
add(1, 2, 3);
3、ES5 实现继承
在一个子构造函数中,可以通过调用父构造函数的call方法来实现继承
function Person(name) {
this.name = name;
}
function Student(name, grade) {
Person.call(this, name);
this.grade = grade;
}
const p1 = new Person("zhangsan");
const s1 = new Student("zhangsan", 100);
4、处理回调函数this丢失问题
执行下面的代码,回调函数会导致 this 丢失。
const obj = {
userName: "zhangsan",
sayName() {
console.log(this.userName);
},
};
obj.sayName(); // 输出 'zhangsan'
function fn(callback) {
if (typeof callback === "function") {
callback();
}
}
fn(obj.sayName); // 输出 undefined
导致这样现象的原因是回调函数执行的时候 this 指向已经是 window 了,所以输出 undefined。
可以使用 call 改变 this 指向,代码如下:
const obj = {
userName: "zhangsan",
sayName() {
console.log(this.userName);
},
};
obj.sayName(); // 输出 'zhangsan'
function fn(callback, context) {
// 定义一个 context 参数,可以把上下文传进去
if (typeof callback === "function") {
callback.call(context); // 显式改变 this 值,指向传入的 context
}
}
fn(obj.sayName, obj); // 输出 'zhangsan'
十三届国赛:分一分
介绍
如果给你一个数组,你能很快将它分割成指定长度的若干份吗?
本题需要在已提供的基础项目中使用 JS 知识封装一个函数,达到分割数组的要求。
准备
开始答题前,需要先打开本题的项目代码文件夹,目录结构如下:
├── effect.gif
├── index.html
└── js
└── index.js
其中:
effect.gif 是最终实现的效果图。
index.html 是主页面。
js/index.js 是需要补充代码的 js 文件。
注意:打开环境后发现缺少项目代码,请手动键入下述命令进行下载:
cd /home/project
wget https://labfile.oss.aliyuncs.com/courses/9788/01.zip && unzip 01.zip && rm 01.zip
在浏览器中预览 index.html 页面,显示如下所示:

目标
请在 js/index.js 文件中补全函数 splitArray 中的代码,最终返回按指定长度分割的数组。
具体要求如下:
将待分割的(一维)数组升序排序。
将排序后的数组从下标为 0 的元素开始,按照从 id=sliceNum 的输入框中获取到的数值去分割,并将分割好的数据存入一个新数组中。如:输入框中值为 n,将原数组按每 n 个一组分割,不足 n 个的数据为一组。
将得到的新数组返回(即 return 一个二维数组)。
例如:
var arr = [3, 1, 4, 2, 5, 6, 7];
// 分割成每 1 个一组
var newA = splitArray(arr, 1);
console.log(newA); // => [[1],[2],[3],[4],[5],[6],[7]]
// 分割成每 2 个一组
newA = splitArray(arr, 2);
console.log(newA); // => [[1,2],[3,4],[5,6],[7]]
// 分割成每 4 个一组
newA = splitArray(arr, 4);
console.log(newA); // => [[1,2,3,4],[5,6,7]]
// 分割成每 7 个一组
newA = splitArray(arr, 7);
console.log(newA); // => [[1,2,3,4,5,6,7]]
上述仅为示例代码,判题时会随机提供数组对该函数功能进行检测。
完成后的效果见文件夹下面的 gif 图,图片名称为 effect.gif(提示:可以通过 VS Code 或者浏览器预览 gif 图片)。
规定
请勿修改 js/index.js 文件外的任何内容。
请严格按照考试步骤操作,切勿修改考试默认提供项目中的文件名称、文件夹路径、class 名、id 名、图片名等,以免造成无法判题通过。
判分标准
完全实现题目目标得满分,否则得 0 分。
答案:
/**
* @param {Object} oldArr
* @param {Object} num
* */
const splitArray = (oldArr, num) => {
// TODO:请补充代码实现功能
var newArr = [];
var temp = num;
for(var i = 0; i < oldArr.length; i+=temp){
newArr.push(oldArr.sort((a,b)=>a-b).slice(i,num));
num+= temp;
}
return newArr;
};
module.exports = splitArray; // 检测需要,请勿删除