蓝桥杯js复习

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

查看事件类型

keyCode

查看键值码

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 中的代码,最终返回按指定长度分割的数组。

具体要求如下:

  1. 将待分割的(一维)数组升序排序。

  1. 将排序后的数组从下标为 0 的元素开始,按照从 id=sliceNum 的输入框中获取到的数值去分割,并将分割好的数据存入一个新数组中。如:输入框中值为 n,将原数组按每 n 个一组分割,不足 n 个的数据为一组。

  1. 将得到的新数组返回(即 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; // 检测需要,请勿删除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值