Loading... # 2023/1/30 <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-455abaf05dda2808cae7f396818fd8e127" aria-expanded="true"><div class="accordion-toggle"><span style="">介绍一下JS有哪些内置对象</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-455abaf05dda2808cae7f396818fd8e127" class="collapse collapse-content"><p></p>JS中的内置对象主要指的是在程序执行前存在全局作用域里的由JS定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。一般我们经常用到的如全局变量值NaN、undefined,全局函数如parseInt()、parseFloat()用来实例化对象的构造函数如Date、Object等,还有提供数学计算的单体内置对象如Math对象<p></p></div></div></div> --- <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-f0aef6767b79d5b15a6cb44757fca91833" aria-expanded="true"><div class="accordion-toggle"><span style="">JavaScript的Dom节点操作:创建、插入、删除、复制、查找</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-f0aef6767b79d5b15a6cb44757fca91833" class="collapse collapse-content"><p></p> + 创建节点、追加节点 1. createElement (标签名)创建一个元素节点 2. createTextNode(节点文本内容)创建一个文本节点 3. createDocumentFragment()创建一个Dom片段 4. appendChild(节点)追加一个节点 + 插入节点 1. appendChild(节点)也是一种插入节点的方式,还可以添加已经存在的元素,会将其元素从原来的位置移到新的位置 2. insertBefore(a,b)是参照节点,意思是a节点会插入b节点的前面 + 删除、移除节点 1. removeChild(节点)删除一个节点,用于将移除删除一个参数节点。其返回的被移除的节点,被移除的节点仍在文档中,只是文档中已没有其位置了。 + 复制节点 cloneNode()方法,用于复制节点,接受一个布尔值参数,true表示深复制,false表示浅复制。 + 查找节点 1. getElementsBytagName()通过标签名称 2. getElementsByName()通过元素的Name属性值 3. getElementById()通过元素的id <p></p></div></div></div> --- <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-00a6f58e991564c7087f731c0d6af71977" aria-expanded="true"><div class="accordion-toggle"><span style="">清除浮动有几种方式</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-00a6f58e991564c7087f731c0d6af71977" class="collapse collapse-content"><p></p> 1. 父级div定义height + 原理:手动给父级元素定义height,就解决了父级元素无法自动获取高度的问题 + 有点:简单 + 缺点:只适合高度固定的布局 2. 结尾处加空的div标签clear:both + 原理:在浮动的元素后边加一个空的div,然后利用css提供的clear:both;清除浮动。会让父级div自动获得高度 + 优点:不用确定父元素的高度 + 缺点:如果浮动元素过多的话,会出现太多的div ``` <div class="fa1" style="float: left; width: 300px;height: 300px;"> 浮动元素 </div> <!-- 2使用css3的属性 clear:both; --> <div style="clear: both;"> </div> ``` 3. 父级定义伪类:after和zoom;最适合的,兼容各种情况,可以服用 ``` /* 伪类:after是指在.fa选择的元素后边加元素 */ .fa:after{ /* 加入元素的内容 */ content: ""; /* 将元素设置为块元素 */ display: block; /* 将元素本身隐藏 */ visibility: hidden; /* 设置元素的高度,如果没有内容可以不设置 */ height: 0px; /* 清除浮动 */ clear: both; /* 超出部分隐藏 */ overflow: hidden; } /* 不支持伪类的元素,使用zoom缩放属性让div远离浮动的破坏 */ .fa{ zoom: 1; } ``` 4. 父元素设置overflow:hidden可以清除浮动 + 缺点:超出盒子的部分会被隐藏 5. 双伪元素法 + 最好用 + 在要清除浮动的元素的前边和后边设置 ``` .fa:before, .fa:after{ content: ""; display: block; clear: both; } .fa{ zoom: 1; } ``` <p></p></div></div></div> --- <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-f9ca79d8bfbc8f06abc6696c7f509caf66" aria-expanded="true"><div class="accordion-toggle"><span style="">JavaScript内置的常用对象有哪些?并列举该对象常用的方法?</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-f9ca79d8bfbc8f06abc6696c7f509caf66" class="collapse collapse-content"><p></p> 1. Arguments 函数参数集合 * arguments[ ] 函数参数的数组 * Arguments 一个函数的参数和其他属性 * Arguments.callee 当前正在运行的函数 * Arguments.length 传递给函数的参数的个数 2. Array 数组 * length属性 动态获取数组长度 * join() 将一个数组转成字符串。返回一个字符串。 * reverse() 将数组中各元素颠倒顺序 * delete运算符 只能删除数组元素的值,而所占空间还在,总长度没变(arr.length)。 * shift() 删除数组中第一个元素,返回删除的那个值,并将长度减 1。 * pop() 删除数组中最后一个元素,返回删除的那个值,并将长度减1。 * unshift() 往数组前面添加一个或多个数组元素,长度要改变。 * push() 往数组结尾添加一个或多个数组元素,长度要改变。 * concat( ) 连接数组 * slice( ) 返回数组的一部分 * sort( ) 对数组元素进行排序 * splice( ) 插入、删除或替换数组的元素 * toLocaleString( ) 把数组转换成局部字符串 * toString( ) 将数组转换成一个字符串 3. Boolean 布尔对象 * Boolean.toString( ) 将布尔值转换成字符串 * Boolean.valueOf( ) Boolean对象的布尔值 4. Error异常对象 * Error.message 可以读取的错误消息 * Error.name 错误的类型 * Error.toString( ) 把Error 对象转换成字符串 * EvalError 在不正确使用 eval()时抛出 * SyntaxError 抛出该错误用来通知语法错误 * RangeError 在数字超出合法范围时抛出 * ReferenceError 在读取不存在的变量时抛出 * TypeError 当一个值的类型错误时,抛出该异常 * URIError 由URl的编码和解码方法抛出 5. Function 函数构造器 * Function 函数构造器 * Function.apply( ) 将函数作为一个对象的方法调用 * Function.arguments[] 传递给函数的参数 * Function.call( ) 将函数作为对象的方法调用 * Function.caller 调用当前函数的函数 * Function.length 已声明的参数的个数 * Function.prototype 对象类的原型 * Function.toString( ) 把函数转换成字符串 6. Math 数学对象 * Math对象是一个静态对象 * Math.PI 圆周率。 * Math.abs() 绝对值。 * Math.ceil() 向上取整(整数加 1,小数去掉)。 * Math.floor() 向下取整(直接去掉小数)。 * Math.round() 四舍五入。 * Math.pow(x,y) 求 x的y次方。 * Math.sqrt() 求平方根。 7. Number 数值对象 * Number.MAX_VALUE 最大数值 * Number.MIN_VALUE 最小数值 * Number.NaN 特殊的非数字值 * Number.NEGATIVE_INFINITY 负无穷大 * Number.POSITIVE_INFINITY 正无穷大 * Number.toExponential( ) 用指数计数法格式化数字 * Number.toFixed( ) 采用定点计数法格式化数字 * Number.toLocaleString( ) 把数字转换成本地格式的字符串 * Number.toPrecision( ) 格式化数字的有效位 * Number.toString( ) 将—个数字转换成字符串 * Number.valueOf( ) 返回原始数值 8. Object 基础对象 * Object 含有所有 JavaScript 对象的特性的超类 * Object.constructor 对象的构造函数 * Object.hasOwnProperty( ) 检查属性是否被继承 * Object.isPrototypeOf( ) 一个对象是否是另一个对象的原型 * Object.propertyIsEnumerable( ) 是否可以通过 for/in循环看到属性 * Object.toLocaleString( ) 返回对象的本地字符串表示 * Object.toString( ) 定义一个对象的字符串表示 * Object.valueOf( ) 指定对象的原始值 9. RegExp 正则表达式对象 * RegExp.exec( ) 通用的匹配模式 * RegExp.global 正则表达式是否全局匹配 * RegExp.ignoreCase 正则表达式是否区分大小写 * RegExp.lastIndex 下次匹配的起始位置 * RegExp.source 正则表达式的文本 * RegExp.test( ) 检测一个字符串是否匹配某个模式 * RegExp.toString( ) 把正则表达式转换成字符串 10. String 字符串对象 * Length 获取字符串的长度。 * toLowerCase() 将字符串中的字母转成全小写。 * toUpperCase() 将字符串中的字母转成全大写。 * charAt(index) 返回指定下标位置的一个字符。如果没有找到,则返回空字符串。 * substr() 在原始字符串,返回一个子字符串 * substring() 在原始字符串,返回一个子字符串。 * split() 将一个字符串转成数组。 * charCodeAt( ) 返回字符串中的第 n个字符的代码 * concat( ) 连接字符串 * fromCharCode( ) 从字符编码创建—个字符串 * indexOf( ) 返回一个子字符串在原始字符串中的索引值(查找顺序从左往右查找)。如果没 有找到,则返回-1。 * lastIndexOf( ) 从后向前检索一个字符串 * localeCompare( ) 用本地特定的顺序来比较两个字符串 * match( ) 找到一个或多个正则表达式的匹配 * replace( ) 替换一个与正则表达式匹配的子串 * search( ) 检索与正则表达式相匹配的子串 * slice( ) 抽取一个子串 * toLocaleLowerCase( ) 把字符串转换小写 * toLocaleUpperCase( ) 将字符串转换成大写 * toLowerCase( ) 将字符串转换成小写 * toString( ) 返回字符串 * toUpperCase( ) 将字符串转换成大写 * valueOf( ) 返回字符串 <p></p></div></div></div> --- <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-97fff15c87ad8d1e0f2dd177c7bf452b31" aria-expanded="true"><div class="accordion-toggle"><span style="">splice和split的区别</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-97fff15c87ad8d1e0f2dd177c7bf452b31" class="collapse collapse-content"><p></p> + splice 是一个专门用于 数组操作 的方法,堪称最强大的数组操作方法。它可以对数组中的元素进行删除、插入和替换。替换原数组,返回删除的元素数组。 + split:以基于指定的分隔符将一个字符串分割成 多个子字符串,并将结果放在一个数组中 <p></p></div></div></div> # 2023/1/31 + 问题 请写出下面代码自行结果? + 内容 ``` console.log(1); setTimeout(() => { console.log(2); process.nextTick(() => { console.log(3); }); new Promise((resolve) => { console.log(4); resolve(); }).then(() => { console.log(5); }); }); new Promise((resolve) => { console.log(7); resolve(); }).then(() => { console.log(8); }); process.nextTick(() => { console.log(6); }); setTimeout(() => { console.log(9); process.nextTick(() => { console.log(10); }); new Promise((resolve) => { console.log(11); resolve(); }).then(() => { console.log(12); }); }); ``` + 答案 1 7 6 8 2 4 3 5 9 11 10 12 --- <div class="panel panel-default collapse-panel box-shadow-wrap-lg"><div class="panel-heading panel-collapse" data-toggle="collapse" data-target="#collapse-fc18cb96346e578043f5916f39945e1c31" aria-expanded="true"><div class="accordion-toggle"><span style="">解析</span> <i class="pull-right fontello icon-fw fontello-angle-right"></i> </div> </div> <div class="panel-body collapse-panel-body"> <div id="collapse-fc18cb96346e578043f5916f39945e1c31" class="collapse collapse-content"><p></p> * 宏任务和微任务 * 宏任务:macrotask,包括setTimeout、setInerVal、setImmediate(node独有)、requestAnimationFrame(浏览器独有)、I/O、UI rendering(浏览器独有) * 微任务:microtask,包括process.nextTick(Node独有)、Promise.then()、Object.observe、MutationObserver * Promise构造函数中的代码是同步执行的,new Promise()构造函数中的代码是同步代码,并不是微任务 * Node.js中的EventLoop执行宏队列的回调任务有**6个阶段** * 1.timers阶段:这个阶段执行setTimeout和setInterval预定的callback * 2.I/O callback阶段:执行除了close事件的callbacks、被timers设定的callbacks、setImmediate()设定的callbacks这些之外的callbacks * 3.idle, prepare阶段:仅node内部使用 * 4.poll阶段:获取新的I/O事件,适当的条件下node将阻塞在这里 * 5.check阶段:执行setImmediate()设定的callbacks * 6.close callbacks阶段:执行socket.on('close', ....)这些callbacks * NodeJs中宏队列主要有4个 * 1.Timers Queue * 2.IO Callbacks Queue * 3.Check Queue * 4.Close Callbacks Queue * 这4个都属于宏队列,但是在浏览器中,可以认为只有一个宏队列,所有的macrotask都会被加到这一个宏队列中,但是在NodeJS中,不同的macrotask会被放置在不同的宏队列中。 * NodeJS中微队列主要有2个 * 1.Next Tick Queue:是放置process.nextTick(callback)的回调任务的 * 2.Other Micro Queue:放置其他microtask,比如Promise等 * 在浏览器中,也可以认为只有一个微队列,所有的microtask都会被加到这一个微队列中,但是在NodeJS中,不同的microtask会被放置在不同的微队列中。 * Node.js中的EventLoop过程 * 1.执行全局Script的同步代码 * 2.执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务 * 3.开始执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有任务,注意,这里是所有每个阶段宏任务队列的所有任务,在浏览器的Event Loop中是只取宏队列的第一个任务出来执行,每一个阶段的macrotask任务执行完毕后,开始执行微任务,也就是步骤2 * 4.Timers Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue ...... * 5.这就是Node的Event Loop * Node 11.x新变化 * 现在node11在timer阶段的setTimeout,setInterval...和在check阶段的immediate都在node11里面都修改为一旦执行一个阶段里的一个任务就立刻执行微任务队列。为了和浏览器更加趋同. <p></p></div></div></div> # 2023/02/01 + 问题1 写出以下代码的执行结果 ``` function side(arr) { arr[0] = arr[2]; } function a(a, b, c = 3) { c = 10; side(arguments); return a + b + c; } a(1, 1, 1); ``` + 答案 12 + 问题2 ``` function side(arr) { arr[0] = arr[2]; } function a(a, b, c ) { c = 10; side(arguments); return a + b + c; } a(1, 1, 1); ``` + 答案 21 + 解析 加了默认值,则转为严格模式(其实也可以使用 "use strict" 转),这时候参数(a、b、c)与 arguments 没有绑定关系,所以修改 arguments 不会影响到参数(a、b、c)的值,修改参数(a、b、c)也不会影响到 arguments。 不加默认值,则为非严格模式,结果和上面的相反。 # 2023/02/02 1. 问题 写出下面代码执行结果 ``` var min = Math.min(); max = Math.max(); console.log(min < max); ``` 2. 答案 false 3. 解析 * 按常规的思路,这段代码应该输出 true,毕竟最小值小于最大值。但是却输出 false * MDN 相关文档是这样解释的 * Math.min 的参数是 0 个或者多个,如果多个参数很容易理解,返回参数中最小的。如果没有参数,则返回 Infinity,无穷大。 * 而 Math.max 没有传递参数时返回的是-Infinity.所以输出 false # 2023/02/03 1. 问题 写出下面代码执行结果并说明原因 ``` var a = 1; (function a() { a = 2; console.log(a); })(); ``` 2. 答案 输出a函数本身 3. 解析 立即执行的函数表达式(IIFE)的函数名称跟内部变量名称重名后,函数名称优先,因为函数名称是不可改变的,内部会静默失败,在严格模式下会报错 ``` var a = 1; (function a () { 'use strict'; a = 2; console.log(a); })(); VM1059:4 Uncaught TypeError: Assignment to constant variable. at a (<anonymous>:4:7) at <anonymous>:6:3 ``` # 2023/02/06 1. 问题 写出下面代码执行结果 ``` var a = [0]; if (a) { console.log(a == true); } else { console.log(a); } ``` 2. 答案 false 3. 解析 > 1)当 a 出现在 if 的条件中时,被转成布尔值,而 Boolean([0])为 true,所以就进行下一步判断 a == true,在进行比较时,[0]被转换成了 0,所以 0==true 为 false > 数组从非 primitive 转为 primitive 的时候会先隐式调用 join 变成“0”,string 和 boolean 比较的时候,两个都先转为 number 类型再比较,最后就是 0==1 的比较了 > > ``` > var a = [1]; > if (a) { > console.log(a == true); > } else { > console.log(a); > } > // true > ``` > > ``` > !![] //true 空数组转换为布尔值是 true, > !![0]//true 数组转换为布尔值是 true > [0] == true;//false 数组与布尔值比较时却变成了 false > Number([])//0 > Number(false)//0 > Number(['1'])//1 > ``` > > 2)所以当 a 出现在 if 的条件中时,被转成布尔值,而 Boolean([0])为 true,所以就进行下一步判断 a == true,在进行比较时,js 的规则是: > ①如果比较的是原始类型的值,原始类型的值会转成数值再进行比较 > > ``` > 1 == true //true 1 === Number(true) > 'true' == true //false Number('true')->NaN Number(true)->1 > '' = 0//true > '1' == true//true Number('1')->1 > ``` > > ②对象与原始类型值比较,对象会转换成原始类型的值再进行比较。 > ③undefined和null与其它类型进行比较时,结果都为false,他们相互比较时结果为true。 数组从非 primitive 转为 primitive 的时候会先隐式调用 join 变成“0” 这一句解释应该有点问题,应该是隐式调用数组的toString方法。[0].toString() // '0', 只不过这里和[0].join() 产生一样的结果。 [0].toString() 和 [0].join(',') 和 [0].join() 都返回 '0' 顺便说一下,非 primitive 都是通过调用自身的 valueOf、和toString 来进行隐式转换的。 # 2023/02/07 1. 问题 写出下面代码执行结果 ``` (function () { var a = (b = 5); })(); console.log(b); console.log(a); ``` 2. 答案 5 undefined 3. 解析 在这个立即执行函数表达式(IIFE)中包括两个赋值操作,其中`a`使用`var`关键字进行声明,因此其属于函数内部的局部变量(仅存在于函数中),相反,`b`被分配到全局命名空间。 另一个需要注意的是,这里没有在函数内部使用[严格模式](http://cjihrig.com/blog/javascripts-strict-mode-and-why-you-should-use-it/)(`use strict`;)。如果启用了严格模式,代码会在输出 b 时报错`Uncaught ReferenceError: b is not defined`,需要记住的是,严格模式要求你显式的引用全局作用域。因此,你需要写成: ``` (function () { "use strict"; var a = (window.b = 5); })(); console.log(b); ``` # 2023/02/07 1. 问题 写出下面代码执行结果 ``` var fullname = "a"; var obj = { fullname: "b", prop: { fullname: "c", getFullname: function () { return this.fullname; }, }, }; console.log(obj.prop.getFullname()); var test = obj.prop.getFullname; console.log(test()); ``` 2. 答案 c a 3. 解析 * 原因在于`this`指向的是函数的执行环境,`this`取决于其被谁调用了,而不是被谁定义了。 * 对第一个`console.log()`语句而言,`getFullName()`是作为`obj.prop`对象的一个方法被调用的,因此此时的执行环境应该是这个对象。另一方面,但`getFullName()`被分配给`test`变量时,此时的执行环境变成全局对象(`window`),原因是`test`是在全局作用域中定义的。因此,此时的`this`指向的是全局作用域的`fullname`变量,即a。 # 2023/02/09 1. 问题 写出下面代码执行结果 ``` var company = { address: "beijing", }; var yideng = Object.create(company); delete yideng.address; console.log(yideng.address); ``` 2. 答案 输出beijing 3. 解析 这里的 yideng 通过 prototype 继承了 company的 address。yideng自己并没有address属性。所以delete操作符的作用是无效的。 **知识点** 1.delete使用原则:delete 操作符用来删除一个对象的属性。 2.delete在删除一个不可配置的属性时在严格模式和非严格模式下的区别: (1)在严格模式中,如果属性是一个不可配置(non-configurable)属性,删除时会抛出异常; (2)非严格模式下返回 false。 3.delete能删除隐式声明的全局变量:这个全局变量其实是global对象(window)的属性 4.delete能删除的: (1)可配置对象的属性(2)隐式声明的全局变量 (3)用户定义的属性 (4)在ECMAScript 6中,通过 const 或 let 声明指定的 "temporal dead zone" (TDZ) 对 delete 操作符也会起作用 delete不能删除的: (1)显式声明的全局变量 (2)内置对象的内置属性 (3)一个对象从原型继承而来的属性 5.delete删除数组元素: (1)当你删除一个数组元素时,数组的 length 属性并不会变小,数组元素变成undefined (2)当用 delete 操作符删除一个数组元素时,被删除的元素已经完全不属于该数组。 (3)如果你想让一个数组元素的值变为 undefined 而不是删除它,可以使用 undefined 给其赋值而不是使用 delete 操作符。此时数组元素是在数组中的 6.delete 操作符与直接释放内存(只能通过解除引用来间接释放)没有关系。 7.其它例子 (1)下面代码输出什么? ``` var output = (function(x){ delete x; return x; })(0); console.log(output); ``` 答案:0,`delete` 操作符是将object的属性删去的操作。但是这里的 `x` 是并不是对象的属性, `delete` 操作符并不能作用。 (2)下面代码输出什么? ``` var x = 1; var output = (function(){ delete x; return x; })(); console.log(output); ``` 答案:输出是 1。delete 操作符是将object的属性删去的操作。但是这里的 x 是并不是对象的属性, delete 操作符并不能作用。 (3)下面代码输出什么? ``` x = 1; var output = (function(){ delete x; return x; })(); console.log(output); ``` 答案:报错 VM548:1 Uncaught ReferenceError: x is not defined, (4)下面代码输出什么? ``` var x = { foo : 1}; var output = (function(){ delete x.foo; return x.foo; })(); console.log(output); ``` 答案:输出是 undefined。x虽然是全局变量,但是它是一个object。delete作用在x.foo上,成功的将x.foo删去。所以返回undefined # 2023/02/10 1. 问题 ``` var foo = function bar() { return 12; }; console.log(typeof bar()); ``` 2. 答案 报错 3. 解析 这种命名函数表达式函数只能在函数体内有效 ``` var foo = function bar(){ // foo is visible here // bar is visible here console.log(typeof bar()); // Work here :) }; // foo is visible here // bar is undefined here ``` # 2023/02/13 1. 问题 写出代码执行结果并说明原因 ``` var x=1; if(function f(){}){ x += typeof f; } console.log(x) // 写出执行结果,并解释原因 ``` 2. 结果 1 undefined 3. 解析 条件判断为假的情况有:0,false,'',null,undefined,未定义对象。函数声明写在条件判断中,其为true,但放在条件判断中的函数声明在执行阶段是找不到的。另外,对未声明的变量执行typeOf不会报错,会返回undefined # 2023/02/14 1. 问题 写出下面代码执行结果 ``` function f(){ return f; } console.log(new f() instanceof f); ``` 2. 答案 false 3. 解析 a instance of b的含义是a是否为b的实例,上面new f()返回的是函数f对象并不是实例,因此为false。如果没有return f则为true,即: ``` function f(){ } console.log(new f() instanceof f); ``` # 2023/02/16 1. 问题 写出下面代码执行结果 var foo = { bar: function(){ return this.baz; }, baz:1 } console.log(typeof (f=foo.bar)()); 2. 答案 undefined 3. 解析 因为foo.bar指向的是foo对象中的bar函数,然后赋值给f,f()相当调用foo对象中的bar方法,而调用对象是window,所以this指向window,进而返回的this.baz是undefined。 # 2023/02/17 1. 问题 关于AMD和CMD区别说法正确的是? A.AMD规范:是 RequireJS在推广过程中对模块定义的规范化产出的 B.CMD规范:是SeaJS 在推广过程中对模块定义的规范化产出的 C.CMD 推崇依赖前置;AMD 推崇依赖就近 D.CMD 是提前执行;AMD 是延迟执行 E.AMD性能好,因为只有用户需要的时候才执行;CMD用户体验好,因为没有延迟,依赖模块提前执行了 2. 答案 A、B 3. 解析 C.CMD 推崇依赖就近;AMD 推崇依赖前置 D.CMD 是延迟执行;AMD 是提前执行 E.CMD性能好,因为只有用户需要的时候才执行;AMD用户体验好,因为没有延迟,依赖模块提前执行了 4. 有关AMD和CMD的详细介绍 + AMD和CMD都是JavaScript模块化规范,用于管理和组织JavaScript代码,以实现更好的可维护性和可重用性。以下是它们的介绍: **AMD规范** AMD(Asynchronous Module Definition)是RequireJS库提出的一种模块化规范,主要用于在浏览器环境中异步加载JavaScript模块。其核心思想是模块定义时就指定依赖,从而在使用该模块时异步加载依赖,避免在加载时阻塞页面渲染。 AMD规范中定义了两个核心API:define()和require()。 define()函数用于定义模块,它接受三个参数:模块名、依赖列表和模块定义函数。依赖列表是一个数组,包含该模块所依赖的其他模块,而模块定义函数则返回该模块的接口对象。 require()函数用于在其他模块中引用该模块,它接受一个依赖列表和一个回调函数。依赖列表与define()函数中的依赖列表类似,而回调函数接受的参数就是该模块的接口对象,可以在回调函数中对该模块进行操作。 AMD规范支持异步加载模块,可以使用require()函数动态加载模块。这种方式可以提高应用程序的性能和可维护性,但是需要在编写代码时注意依赖关系的处理。 **CMD规范** CMD(Common Module Definition)是SeaJS库提出的一种模块化规范,主要用于在浏览器环境和Node.js环境中管理和组织JavaScript代码。它与AMD规范相似,但更加注重就近依赖加载和延迟执行。 CMD规范中定义了两个核心API:define()和require(),与AMD规范类似。但是,CMD规范中的define()函数会延迟执行模块定义函数,直到依赖的模块都加载完成后再执行。这种就近依赖加载的方式可以减少模块之间的耦合,提高代码的可维护性。 与AMD规范相比,CMD规范更加灵活,但需要编写更多的代码来处理依赖关系。它适用于大型应用程序的开发,可以帮助开发者更好地组织和管理代码。 # 2023/02/20 1. 问题 关于 SPA 单页页面的理解正确的是? ``` A.用户体验好、快,但是内容的改变需要重新加载整个页面,会造成不必要的跳转和重复渲染; B.前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理; C.初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载; D.前进后退路由管理需要使用浏览器的前进后退功能 E.SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。 ``` 2. 答案 B、C、E 3. 解析 SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。 + SPA优点 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染; 基于上面一点,SPA 相对对服务器压力小; 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理; + SPA缺点 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载; 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理; SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。 # 2023/02/21 1. 问题 下面对 Vue.js 中 keep-alive 的理解正确的是? ``` A.一般结合路由和动态组件一起使用,用于缓存组件; B.提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 include 的优先级比 exclude 高; C.对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。 D.keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,但是不能避免重新渲染 ``` 2. 答案 A、C 3. 解析 B: include的优先级比 exclude 低; D:能避免重新渲染 # 2023/02/22 1. 问题 关于 Vue.js 虚拟 DOM 的优缺点说法正确的是? ``` A.可以保证性能下限,比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限; B.无需手动操作DOM,不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率; C.可以进行极致优化: 虚拟 DOM + 合理的优化,可以使性能达到极致 D.可以跨平台,虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。 ``` 2. 答案 A、B、D 3. 解析 1)优点 + 保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限; + 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率; + 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。 2)缺点 无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。比如VScode采用直接手动操作DOM的方式进行极端的性能优化 # 2023/02/23 1. 问题 下面代码输出什么? ``` for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 1); } ``` 2. 答案 0、1、2 3. 解析 使用let关键字声明变量i:使用let(和const)关键字声明的变量是具有块作用域的(块是{}之间的任何东西)。 在每次迭代期间,i将被创建为一个新值,并且每个值都会存在于循环内的块级作用域。 // 下面代码输出什么 for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 1); } 答案:3 3 3,由于JavaScript中的事件执行机制,setTimeout函数真正被执行时,循环已经走完。 由于第一个循环中的变量i是使用var关键字声明的,因此该值是全局的。 在循环期间,我们每次使用一元运算符++都会将i的值增加1。 因此在第一个例子中,当调用setTimeout函数时,i已经被赋值为3。 # 2023/02/24 1. 问题 写出执行结果,并解释原因 ``` const num = { a: 10, add() { return this.a + 2; }, reduce: () => this.a -2; }; console.log(num.add()); console.log(num.reduce()); ``` 2. 答案 12、NaN 3. 解析 注意,add是普通函数,而reduce是箭头函数。对于箭头函数,this关键字指向是它所在上下文(定义时的位置)的环境,与普通函数不同! 这意味着当我们调用reduce时,它不是指向num对象,而是指其定义时的环境(window)。没有值a属性,返回undefined。 # 2023/02/27 1. 问题 写出执行结果并说明原因 ``` const person = { name: "yideng" }; function sayHi(age) { return `${this.name} is ${age}`; } console.log(sayHi.call(person, 21)); console.log(sayHi.bind(person, 21)); ``` 2. 答案 VM117:6 yideng is 5 VM117:7 ƒ sayHi(age) { return ${this.name} is ${age}; } 3. 解析 这里call方法会立即执行函数,而bind方法只会返回函数并不会立即调用函数,下面适用bind的方式才会返回yideng is 5: ``` sayHi.bind(person,21)() ``` # 2023/02/28 1. 问题 写出执行结果并解释原因 ``` ["1", "2", "3"].map(parseInt); ``` 2. 答案 [1,NaN,NaN] 3. 解析 1)Array.prototype.map() array.map(callback[, thisArg]) callback函数的执行规则 参数:自动传入三个参数 currentValue(当前被传递的元素); index(当前被传递的元素的索引); array(调用map方法的数组) 2)parseInt方法接收两个参数 第三个参数["1", "2", "3"]将被忽略。parseInt方法将会通过以下方式被调用 parseInt("1", 0) parseInt("2", 1) parseInt("3", 2) 3)parseInt的第二个参数radix为0时,ECMAScript5将string作为十进制数字的字符串解析; parseInt的第二个参数radix为1时,解析结果为NaN; parseInt的第二个参数radix在2—36之间时,如果string参数的第一个字符(除空白以外),不属于radix指定进制下的字符,解析结果为NaN。 parseInt("3", 2)执行时,由于"3"不属于二进制字符,解析结果为NaN。 # 2023/03/01 1. 写出执行结果并解释原因 ``` [typeof null, null instanceof Object]; ``` 2. 答案 [object,false] 3. 解析 1) typeof 返回一个表示类型的字符串. typeof 的结果列表 Undefined "undefined" Null "object" Boolean "boolean" Number "number" String "string" Symbol "symbol" Function "function" Object "object" 2)instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上. # 2023/03/02 1. 写出执行结果并说明原因 ``` function f() {} const a = f.prototype, b = Object.getPrototypeOf(f); console.log(a === b); ``` 2. 答案 false 3. 解析 + f.prototype 是使用使用 new 创建的 f 实例的原型. 而 Object.getPrototypeOf 是 f 函数的原型. + a === Object.getPrototypeOf(new f()) // true + b === Function.prototype // true # 2023/03/03 1. 写出执行结果,并解释原因 ``` function showCase(value) { switch (value) { case "A": console.log("Case A"); break; case "B": console.log("Case B"); break; case undefined: console.log("undefined"); break; default: console.log("Do not know!"); } } showCase(new String("A")); ``` 2. 答案 Do not know! 3. 解析 switch采用的是严格比较,因此不仅比较值还比较类型。 # 2023/03/06 1. 选择正确的答案 ``` console.log([2,1,0].reduce(Math.pow)); console.log([].reduce(Math.pow)); / * A. 2 报错 B. 2 NaN C. 1 报错 D. 1 NaN */ ``` 2. 答案 C 3. 解析 arr.reduce(callback[, initialValue]) reduce接受两个参数, 一个回调, 一个初始值 回调函数接受四个参数 previousValue, currentValue, currentIndex, array 需要注意的是 If the array is empty and no initialValue was provided, TypeError would be thrown. 所以第二个会报异常. 第一个表达式等价于 Math.pow(2, 1) => 2; Math.pow(2, 0) =>1 # 2023/03/07 1. 问题 请问变量 a 会被 GC 吗 ``` function test() { var a = 1; return function () { eval(""); }; } test(); ``` 2. 答案 不会 3. 解析 这个问题涉及到JavaScript中的垃圾回收机制。垃圾回收是一种自动管理内存的机制,它会在程序执行过程中自动回收不再使用的内存,以避免内存泄漏和程序崩溃。 如果变量a在程序执行完后不再被使用,那么它就会被垃圾回收器自动回收,释放占用的内存空间。如果变量a被持续引用或者还有其他的变量引用了它,那么它就不会被垃圾回收器回收。 因此,变量a是否会被垃圾回收取决于它在程序执行过程中是否还被引用。 因为eval会欺骗词法作用域,例如function test(){eval("var a = 1"},创建了一个a变量,不确定eval是否对a进行了引用,所以为了保险,不对其进行优化。相对,try catch,with也不会被回收,with会创建新的作用域。 # 2023/03/08 1. 写出执行结果,并解释原因 ``` const value = "Value is" + !!Number(["0"]) ? "yideng" : "undefined"; console.log(value); ``` 2. 答案 yideng 3. 解析 +优先级大于? 所以原题等价于 'Value is false' ? 'yideng' : undefined'' 而不是 'Value is' + (false ? 'yideng' : 'undefined') # 2023/03/09 1. 问题 写出执行结果,并解释原因 ``` var arr = [0, 1]; arr[5] = 5; newArr = arr.filter(function (x) { return x === undefined; }); console.log(newArr.length); ``` 2. 答案 0 3. 解析 filter 为数组中的每个元素调用一次 callback 函数,并利用所有使得 callback 返回 true 或等价于 true 的值的元素创建一个新数组。callback 只会在已经赋值的索引上被调用,对于那些已经被删除或者从未被赋值的索引不会被调用。那些没有通过 callback 测试的元素会被跳过,不会被包含在新数组中。 也就是说 从 2-4 都是没有初始化的'坑'!, 这些索引并不存在与数组中. 在 array 的函数调用的时候是会跳过这些'坑'的. # 2023/03/09 1. 问题 写出执行结果并解释原因(以新版谷歌浏览器为准) ``` async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { console.log('async2'); } console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0) async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end'); ``` 2. 答案 script start async1 start async2 promise1 script end async1 end promise2 setTimeout 3. 解析 **知识点** 考察的是js中的事件循环和回调队列。注意以下几点 + Promise优先于setTimeout宏任务。所以,setTimeout回调会在最后执行。 + Promise一旦被定义,就会立即执行 + Promise的reject和resolve是异步执行的回调。所以,resolve()会被放到回调队列中,在主函数执行完和setTimeout前调用。 + await执行完后,会让出线程。async标记的函数会返回一个Promise对象 **执行流程分析** + 首先,事件循环从宏任务(macrotask)队列开始,这个时候,宏任务队列中,只有一个script(整体代码)任务;从宏任务队列中取一个任务出来执行。 a.首先执行console.log('script start') ,输出 'script start' 。 b.遇到setTimeout 把 console.log('setTimeout') 放到macroTask队列中。 c.执行async1(), 输出 'async1 start' 和 'async2' ,把 console.log('async1 end') 放到micro 队列中。 d.执行到promise , 输出 'promise1' , 把 console.log('promise2') 放到micro 队列中。 e.执行 console.log('script end') 。输出 'script end' + macrotask 执行完会执行microtask ,把 microtask quene 里面的 microtask 全部拿出来一次性执行完,所以会输出 ‘async1 end’ 和 ‘ promise2’ + 开始新一轮事件循环,去除一个macrotask执行,所以会输出 “setTimeout”。 # 2023/03/13 1. 下面代码中 a 在什么情况下会打印 1 ``` var a = ?; if(a == 1 && a== 2 && a== 3){ console.log(1); } ``` 2. 答案解析 较操作涉及多不同类型的值时候,会涉及到很多隐式转换,其中规则繁多即便是经验老道的程序员也没办法完全记住,特别是用到 == 和 != 运算时候。所以一些团队规定禁用 == 运算符换用=== 严格相等。 答案一 var aᅠ = 1; var a = 2; var ᅠa = 3; if(aᅠ==1 && a== 2 &&ᅠa==3) { console.log("1") } 考察你的找茬能力,注意if里面的空格,它是一个Unicode空格字符,不被ECMA脚本解释为空格字符(这意味着它是标识符的有效字符)。所以它可以解释为 var a_ = 1; var a = 2; var _a = 3; if(a_==1 && a== 2 &&_a==3) { console.log("1") } 答案二 var a = { i: 1, toString: function () { return a.i++; } } if(a == 1 && a == 2 && a == 3) { console.log('1'); } 如果原始类型的值和对象比较,对象会转为原始类型的值,再进行比较。 对象转换成原始类型的值,算法是先调用valueOf方法;如果返回的还是对象,再接着调用toString方法。 答案三 var a = [1,2,3]; a.join = a.shift; console.log(a == 1 && a == 2 && a == 3); 比较巧妙的方式,array也属于对象, 对于数组对象,toString 方法返回一个字符串,该字符串由数组中的每个元素的 toString() 返回值经调用 join() 方法连接(由逗号隔开)组成。 数组 toString 会调用本身的 join 方法,这里把自己的join方法该写为shift,每次返回第一个元素,而且原数组删除第一个值,正好可以使判断成立 答案四 var i = 0; with({ get a() { return ++i; } }) { if (a == 1 && a == 2 && a == 3) console.log("1"); } with 也是被严重建议不使用的对象,这里也是利用它的特性在代码块里面利用对象的 get 方法动态返回 i. 答案五 var val = 0; Object.defineProperty(window, 'a', { get: function() { return ++val; } }); if (a == 1 && a == 2 && a == 3) { console.log('1'); } 全局变量也相当于 window 对象上的一个属性,这里用defineProperty 定义了 a的 get 也使得其动态返回值。和with 有一些类似。 答案六 let a = {[Symbol.toPrimitive]: ((i) => () => ++i) (0)}; if (a == 1 && a == 2 && a == 3) { console.log('1'); } ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。我们之前在定义类的内部私有属性时候习惯用 __xxx ,这种命名方式避免别人定义相同的属性名覆盖原来的属性,有了 Symbol 之后我们完全可以用 Symbol值来代替这种方法,而且完全不用担心被覆盖。 除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。Symbol.toPrimitive就是其中一个,它指向一个方法,表示该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。这里就是改变这个属性,把它的值改为一个 闭包 返回的函数。 业务中一般不会写出这种代码,重点还是知识点的考察 # 2023/03/14 1. 写出执行结果,并解释原因 ``` const obj = { 2: 3, 3: 4, length: 2, splice: Array.prototype.splice, push: Array.prototype.push, }; obj.push(1); obj.push(2); console.log(obj); ``` 2. 答案 Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ] 3. 分析 涉及知识点: 1.类数组(ArrayLike):一组数据,由数组来存,但是如果要对这组数据进行扩展,会影响到数组原型,ArrayLike的出现则提供了一个中间数据桥梁,ArrayLike有数组的特性, 但是对ArrayLike的扩展并不会影响到原生的数组。 2.push方法:push 方法有意具有通用性。该方法和 call() 或 apply() 一起使用时,可应用在类似数组的对象上。push 方法根据 length 属性来决定从哪里开始插入给定的值。如果 length 不能被转成一个数值,则插入的元素索引为 0,包括 length 不存在时。当 length 不存在时,将会创建它。 唯一的原生类数组(array-like)对象是 Strings,尽管如此,它们并不适用该方法,因为字符串是不可改变的。 3.对象转数组的方式:Array.from()、splice()、concat()等 解析 这个obj中定义了两个key值,分别为splice和push分别对应数组原型中的splice和push方法,因此这个obj可以调用数组中的push和splice方法,调用对象的push方法:push(1),因为此时obj中定义length为2,所以从数组中的第二项开始插入,也就是数组的第三项(下表为2的那一项),因为数组是从第0项开始的,这时已经定义了下标为2和3这两项,所以它会替换第三项也就是下标为2的值,第一次执行push完,此时key为2的属性值为1,同理:第二次执行push方法,key为3的属性值为2。此时的输出结果就是:Object(4) [empty × 2, 1, 2, splice: ƒ, push: ƒ],因为只是定义了2和3两项,没有定义0和1这两项,所以前面会是empty。 # 2023/03/15 1. 写出执行结果并解释原因 ``` let a = { n: 1 }; let b = a; a.x = a = { n: 2 }; console.log(a.x); console.log(b.x); ``` 2. 答案 undefined {n:2} 3. 解析 点号的运算优先级大于等于号 赋值操作从右向左 所以, a.x=a={n:2} 可以表示为a.x=(a={n:2}). a.x的值是先计算的,此时a的指向依然是对象{n:1}. a.x的值是括号里的返回值, 所以是在对象{n:1}上添加了x这个属性,属性值为{n:2}. 然后是变量a被重新赋值,指向了对象{n:2}. 但变量b的指向依然是原先的对象. 所以, a.x的值是undefined, b.x的值是{n:2}. # 2023/03/16 1. 写出执行结果,并解释原因 ``` var a1 = {}, b1 = "123", c1 = 123; a1[b1] = "b"; a1[c1] = "c"; console.log(a1[b1]); var a2 = {}, b2 = Symbol("123"), c2 = Symbol("123"); a2[b2] = "b"; a2[c2] = "c"; console.log(a2[b2]); var a3 = {}, b3 = { key: "123" }, c3 = { key: "456" }; a3[b3] = "b"; a3[c3] = "c"; console.log(a3[b3]); ``` 2. 答案 c b c 3. 解析 数字做对象的key会转成字符串, 所以a1 = { '123': 'c' } a2 = { Symbol(123): 'b', Symbol(123): 'c' }, Symbol是唯一的, 所以a2[b2] = b a3 = { [object Object]: 'c' } obj做key会调用toString方法 # 2023/03/17 1. 写出执行结果并解释原因 ``` function Foo() { Foo.a = function () { console.log(1); }; this.a = function () { console.log(2); }; } Foo.prototype.a = function () { console.log(3); }; Foo.a = function () { console.log(4); }; Foo.a(); let obj = new Foo(); obj.a(); Foo.a(); ``` 2. 答案 4 2 1 3. 解析 首先, function Foo() { Foo.a = function() { console.log(1) } this.a = function() { console.log(2) } } 上面这段代码执行完之后,我们有了一个名为Foo的函数,虽然函数里面也有一些代码,但此时它们还不会被执行,接下来 Foo.prototype.a = function() { console.log(3) } 到这里,我们知道了这个函数所有的实例都会有一个叫做a的方法,执行这个方法之后会输出3,再往下看 Foo.a = function() { console.log(4) } 我们又给Foo函数添加了一个静态方法,也叫做a,执行这个方法时会输出4,继续看接下来的代码 Foo.a(); 毫无疑问,调用了Foo函数的静态方法a,刚才也说了会输出4,所以这里会输出一个4 let obj = new Foo(); 然后我们使用new操作符以Foo构造函数创建了一个实例叫做obj,创建实例时会执行构造函数的代码,也就是Foo里面的代码被执行了。首先, Foo.a = function() { console.log(1) } Foo函数的静态方法a被修改了,原来是输出4,修改之后会输出1。接着 this.a = function() { console.log(2) } 使用new操作符创建实例时,构造函数中的this会指向新创建的实例,在这里新创建的实例就是obj,因此obj有了一个方法叫做a,执行这个方法会输出2。同时,在obj的原型链上存在一个同样叫做a的方法,那个方法执行时会输出3,也就是上面通过 Foo.prototype.a = function() { console.log(3) } 声明的方法,显而易见,对于obj来说,自身的方法a会覆盖原型链上的同名方法,因此,在下面的代码中调用obj.a();时会输出2。 最后,再次调用Foo.a();时,由于刚在在创建obj实例时,Foo函数的静态方法a已经被修改了,因此这里执行的是修改后的函数,所以会输出1。 # 2023/03/20 1. 写出执行结果,并解释原因 ``` function user(obj) { obj.name = "京程一灯"; obj = new Object(); obj.name = "精英班"; } let person = new Object(); user(person); console.log(person.name); ``` 2. 答案 京程一灯 3. 解析 function user(obj) { // obj传入的是引用 obj.name = "京程一灯" // 修改引用的值 obj = new Object() // obj 指向了一个新地址 obj.name = "精英班" // 修改的是新对象,没法改变传入的对象 } # 2023/03/21 1. 写出执行结果并解释原因 ``` let x, y; try { throw new Error(); } catch (x) { x = 1; y = 2; console.log(x); } console.log(x); console.log(y); ``` 2. 答案 1、undefined、2 3. 解析 首先,let创建的x、y值为undefined,try中运行代码抛出throw new Error()的错误给catch,即catch中的x,catch中的代码执行,x为传入的异常被赋值为1,随后被concole.log打印,对catch传入的参数只在catch的块级作用域中有效,而y非catch传入的参数,沿作用域链向上查询被赋值为2;在try...catch后打印x为undefined,因为从未被赋值,而打印y为2,因为在try...catch中被赋值(y不属于catch传入的参数) # 2023/03/22 1. 写出执行结果,并解释原因 ``` let length = 10; function fn() { console.log(this.length); } var obj = { length: 5, method: function (fn) { fn(); arguments[0](); }, }; obj.method(fn, 1); ``` 2. 答案 0 2 3. 解析 1)fn()知识点 ①fn()知识点,任意函数里如果嵌套了 非箭头函数,那这个时候 嵌套函数里的 this 在未指定的情况下,应该指向的是 window 对象,所以这里执行fn会打印window.length,但是let声明的变量会形成块级作用域,且不存在声明提升,而var存在声明提升。所以当使用let声明变量时,不存在声明提升,length属性实际上并没有添加到window对象中。 // 例如在浏览器控制台 let a = 1; window.a // undefined var b = 1; window.b // 1 但是这里为什么输出0呢,因为window对象原先上有length属性,所以输出的是原先的值0 ②arguments0知识点 在方法调用(如果某个对象的属性是函数,这个属性就叫方法,调用这个属性,就叫方法调用)中,执行函数体的时候,作为属性访问主体的对象和数组便是其调用方法内 this 的指向。(通俗的说,调用谁的方法 this 就指向谁; arguments[0]指向 fn,所以 arguments[0]() 是作为 arguments对象的属性[0]来调用 fn的,所以 fn 中的 this 指向属性访问主体的对象 arguments;这里this指向arguments。 因为fn作为一个参数存储在arg对象里,argumengts的长度为2,所以输出2 // 例如 [function fn(){console.log(this.length)}]0; // 1 // 数组也是对象,只不过数组对象的整型属性会计入 length 属性中,并被区别对待,这里就是调用数组对象的0属性,函数作为数组对象的属性调用,函数中的this 当然指向这个数组,所以返回数组的length # 2023/03/23 1. 写出执行结果,并解释原因 ``` var a = 10; var foo = { a: 20, bar: function () { var a = 30; return this.a; }, }; console.log(foo.bar()); console.log(foo.bar()); console.log((foo.bar = foo.bar)()); console.log((foo.bar, foo.bar)()); ``` 2. 答案 20 20 10 10 3. 解析 1)第一问 foo.bar() /* foo调用,this指向foo , 此时的 this 指的是foo,输出20 */ 2)第二问 (foo.bar)() /* 给表达式加了括号,而括号的作用是改变表达式的运算顺序,而在这里加与不加括号并无影响;相当于foo.bar(),输出20 */ 3)第三问 (foo.bar=foo.bar)() /* 等号运算, 相当于重新给foo.bar定义,即 foo.bar = function () { var a = 10; return this.a; } 就是普通的复制,一个匿名函数赋值给一个全局变量 所以这个时候foo.bar是在window作用域下而不是foo = {}这一块级作用域,所以这里的this指代的是window,输出10 */ 4)第四问 (foo.bar,foo.bar)() /* 1.逗号运算符, 2.逗号表达式,求解过程是:先计算表达式1的值,再计算表达式2的值,……一直计算到表达式n的值。最后整个逗号表达式的值是表达式n的值。逗号运算符的返回值是最后一个表达式的值。 3.其实这里主要还是经过逗号运算符后,就是纯粹的函数了,不是对象方法的引用,所以这里this指向的是window,输出10 4.第三问,第四问,一个是等号运算,一个是逗号运算,可以这么理解,经过赋值,运算符运算后,都是纯粹的函数,不是对象方法的引用。所以函数指向的this都是windows的。 */ // 知识点 1)默认绑定 ①独立函数调用时,this 指向全局对象(window),如果使用严格模式,那么全局对象无法使用默认绑定, this绑定至 undefined。 2)隐式绑定 ①函数this 是指向调用者 (隐式指向) function foo() { console.log( this.a); } var obj = { a: 2, foo: foo }; obj.foo(); // 2 obj1.obj2.foo(); // foo 中的 this 与 obj2 绑定 ②问题:隐式丢失 描述:隐式丢失指的是函数中的 this 丢失绑定对象,即它会应用第 1 条的默认绑定规则,从而将 this 绑定到全局对象或者 undefined 上,取决于是否在严格模式下运行。 以下情况会发生隐式丢失 - 绑定至上下文对象的函数被赋值给一个新的函数,然后调用这个新的函数时 - 传入回调函数时 3)显示绑定 显式绑定的核心是 JavaScript 内置的 call(..) 和 apply(..) 方法,call 和 apply bind的this第一个参数 (显示指向) 4)new 绑定 构造函数的this 是new 之后的新对象 (构造器) # 2023/03/24 1. 写出执行结果,并解释原因 ``` function getName() { for (let i = 0; i < 5; i++) { setTimeout(function () { console.log(i); }, i * 1000); } return; { name: "京程一灯"; } } console.log(getName()); ``` 2. 答案 undefined 0 1 2 3 4 3. 解析 // 解析: + 第一个点:undefined,这是因为return后换行了,JavaScirpt会在return语句后自动插入了分号。 分号自动添加的情况: (1)如果下一行的开始与本行的结尾连在一起解释,JavaScript就不会自动添加分号; (2)只有下一行的开始于本行的结尾无法放在一起解释,JavaScript引擎才会自动添加分号; (3)如果一行的起首是++或--运算符,则他们后面自动添加分号; (4)如果continue、break、return、throw这四个语句后面,直接跟换行符,则会自动添加分号。 + 第二个点:let的变量除了作用域是在for区块中,而且会为每次循环执行建立新的词法环境(LexicalEnvironment),拷贝所有的变量名称与值到下个循环执行。 因为用let声明的,所以每个i一个作用域。这里如果是用var声明,则最后输出的都是5。 # 2023/03/27 1. 写出执行结果,并解释原因 ``` const num = parseInt("2*4", 10); console.log(num); ``` 2. 答案 2 3. 解析 只返回了字符串中第一个字母. 设定了 进制 后 (也就是第二个参数,指定需要解析的数字是什么进制: 十进制、十六机制、八进制、二进制等等……), parseInt 检查字符串中的字符是否合法. 一旦遇到一个在指定进制中不合法的字符后,立即停止解析并且忽略后面所有的字符。 *就是不合法的数字字符。所以只解析到 2,并将其解析为十进制的2. num的值即为 2 # 2023/03/29 1. 选择正确答案,并解释为什么 ``` const company = { name: "京程一灯" }; Object.defineProperty(company, "address", { value: "北京" }); console.log(company); console.log(Object.keys(company)); /* A. {name:"京程一灯",address:"北京"},["name","age"] B. {name:"京程一灯",address:"北京"},["name"] C. {name:"京程一灯"},["name","age"] D. {name:"京程一灯"},["name","age"] */ ``` 2. 答案 B 3. 解析 通过 defineProperty方法,我们可以给对象添加一个新属性,或者修改已经存在的属性。而我们使用 defineProperty方法给对象添加了一个属性之后,属性默认为 不可枚举(not enumerable). Object.keys方法仅返回对象中 可枚举(enumerable) 的属性,因此只剩下了 name 用 defineProperty方法添加的属性默认不可变。你可以通过 writable, configurable 和 enumerable属性来改变这一行为。这样的话, 相比于自己添加的属性, defineProperty方法添加的属性有了更多的控制权。 # 2023/03/30 1. 写出执行结果,并解释原因 ``` let num = 10; const increaseNumber = () => num++; const increasePassedNumber = (number) => number++; const num1 = increaseNumber(); const num2 = increasePassedNumber(num1); console.log(num1); console.log(num2); ``` 2. 答案 10 10 3. 解析 一元操作符 ++ 先返回 操作值, 再累加 操作值。num1的值是 10, 因为 increaseNumber函数首先返回 num的值,也就是 10,随后再进行 num的累加。 num2是 10因为我们将 num1传入 increasePassedNumber. number等于 10( num1的值。同样道理, ++ 先返回 操作值, 再累加 操作值。) number是 10,所以 num2也是 10. # 2023/03/31 1. 写出执行结果,并解释原因 ``` const value = { number: 10 }; const multiply = (x = { ...value }) => { console.log((x.number *= 2)); }; multiply(); multiply(); multiply(value); multiply(value); ``` 2. 答案 20 20 20 40 3. 解析 在ES6中,我们可以使用默认值初始化参数。如果没有给函数传参,或者传的参值为 "undefined" ,那么参数的值将是默认值。上述例子中,我们将 value 对象进行了解构并传到一个新对象中,因此 x 的默认值为 {number:10} 。 默认参数在调用时才会进行计算,每次调用函数时,都会创建一个新的对象。我们前两次调用 multiply 函数且不传递值,那么每一次 x 的默认值都为 {number:10} ,因此打印出该数字的乘积值为 20。 第三次调用 multiply 时,我们传递了一个参数,即对象 value。*=运算符实际上是 x.number=x.number*2的简写,我们修改了 x.number的值,并打印出值 20。 第四次,我们再次传递 value对象。x.number之前被修改为 20,所以 x.number*=2打印为 40。 # 2023/04/03 1. 写出执行结果,并解释原因 ``` [1, 2, 3, 4].reduce((x, y) => console.log(x, y)); ``` 2. 答案 1 2 undefined 3 undefined 4 3. 解析 reducer 函数接收4个参数: Accumulator (acc) (累计器) Current Value (cur) (当前值) Current Index (idx) (当前索引) Source Array (src) (源数组) reducer 函数的返回值将会分配给累计器,该返回值在数组的每个迭代中被记住,并最后成为最终的单个结果值。 reducer 函数还有一个可选参数 initialValue, 该参数将作为第一次调用回调函数时的第一个参数的值。如果没有提供 initialValue,则将使用数组中的第一个元素。 在上述例子, reduce方法接收的第一个参数(Accumulator)是 x, 第二个参数(Current Value)是 y。 在第一次调用时,累加器 x为 1,当前值 “y”为 2,打印出累加器和当前值:1和 2。 例子中我们的回调函数没有返回任何值,只是打印累加器的值和当前值。如果函数没有返回值,则默认返回 undefined。在下一次调用时,累加器为 undefined,当前值为“3”, 因此 undefined和 3被打印出。 在第四次调用时,回调函数依然没有返回值。累加器再次为 undefined ,当前值为“4”。undefined和 4被打印出。 # 2023/04/04 1. 写出执行结果,并解释原因 ``` // index.js console.log("running index.js"); import { sum } from "./sum.js"; console.log(sum(1, 2)); // sum.js console.log("running sum.js"); export const sum = (a, b) => a + b; ``` 2. 答案 running sum.js running index.js 3 3. 解析 import命令是编译阶段执行的,在代码运行之前。因此这意味着被导入的模块会先运行,而导入模块的文件会后执行。 这是CommonJS中require()和 import之间的区别。使用 require(),可以在运行代码时根据需要加载依赖项。如果我们使用 require而不是import,则running index.js、running sum.js、 3会被依次打印。 # 2023/04/06 1. 写出执行结果,并解释原因 ``` var a = 0; if(true){ a = 10; console.log(a,window.a); function a(){}; console.log(a,window.a); a = 20; console.log(a,window.a); } console.log(a); ``` 2. 答案 10 0 10 10 20 10 10 3. 解析 1)变量提升 变量的提升是以变量作用域来决定的,即全局作用域中声明的变量会提升至全局最顶层,函数内声明的变量只会提升至该函数作用域最顶层。 console.log(a); var a = 10; // 等价于 var a; console.log(a); a = 10; 2)函数提升 ①函数提升,类似变量提升,但是确有些许不同。 ②函数表达式 console.log(a);// undefined var a = function(){}; // 函数表达式不会声明提升,这里输出undefined,是var a变量声明的提升 ③函数声明 函数声明覆盖变量声明 //因为其是一等公民,与其他值地位相同,所以 函数声明会覆盖变量声明 // 如果存在函数声明和变量声明(注意:仅仅是声明,还没有被赋值),而且变量名跟函数名是相同的,那么,它们都会被提示到外部作用域的开头,但是,函数的优先级更高,所以变量的值会被函数覆盖掉。 /*************未赋值的情况***************/ // 变量名与函数名相同 var company; function company () { console.log ("yideng"); } console.log(typeof company); // function,函数声明将变量声明覆盖了 /*************赋值的情况***************/ // 如果这个变量或者函数其中是赋值了的,那么另外一个将无法覆盖它: var company = "yideng"; // 变量声明并赋值 function company () { console.log ("yideng"); } console.log(typeof company); // string // 这个其实再次赋值了 var company; function company(){}; company = 'yideng'; // 被重新赋值 console.log(typeof company); 3)块级作用域的函数声明 // 在块级作用域中的函数声明和变量是不同的 /块级作用域中变量声明******/ console.log(a); //ReferenceError: a is not defined if(true){ a = 10; console.log(a); } console.log(a); // 会报错, /块级作用域函数声明**/ console.log(a); // 这里不报错,是undefined if(true){ console.log(a); // function a function a(){}; } // 上边的代码等价于 var a; // 函数a的声明 console.log(a); // undefined if(true){ function a(){} // 函数a的定义 console.log(a); // function a } /* 这里其实就是函数function a(){}经过预解析之后: 将函数声明提到函数级作用域最前面,var a;// 函数a的声明 然后将函数定义提升到块级作用域最前面, function a(){} 函数a的定义 */ 如果改变了作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,es6在附录B里面规定,浏览器的实现可以不遵守上面规定,有自己的行为方式 ①允许在块级作用域内声明函数 ②函数声明类似于var,即会提升到全局作用域或函数作用域的头部 ③同时,函数声明还会提升到所在的块级作用域的头部。 注意,上面三条规则只对ES6的浏览器实现有效,其它环境的实现不用遵守,还是将块级作用域的函数声明当做let处理 块级作用域函数,就像预先在全局作用域中使用var声明了一个变量,且默认值为undefined。 console.log(a,window.a); // undefined undefined { console.log(a,window.a); // function a(){} undefined function a(){} console.log(a,window.a); // function a(){} function a(){} } console.log(a,window.a); // function a(){} function a(){} 总结: ①块级作用域函数在编译阶段将函数声明提升到全局作用域,并且会在全局声明一个变量,值为undefined。同时,也会被提升到对应的块级作用域顶层。 ②块级作用域函数只有定义声明函数的那行代码执行过后,才会被映射到全局作用域。 4)块级作用域中有同名的变量和函数声明 console.log(window.a,a);//undefined undefined { console.log(window.a,a);//undefined function a(){} function a() {}; a = 10; console.log(window.a,a); //function a(){} 10 }; console.log(window.a,a); //function a(){} function a(){} /* 1.第一个log,块级作用域函数a的声明会被提升到全局作用域,所以不报错,是undefined undefined 2.第二个log,在块级作用域中,由于声明函数a提升到块级作用域顶端,所以打印a = function a(){},而window.a由于并没有执行函数定义的那一行代码,所以仍然为undefined。 3.第三个log,这时已经执行了声明函数定义,所以会把函数a映射到全局作用域中。所以输出function a(){}, 4.第四个log,就是function a(){} function a(){},因为在块级作用域中window.a的值已经被改变了,变成了function a(){} */ 块级作用域函数只有执行函数声明语句的时候,才会重写对应的全局作用域上的同名变量。 // 解析 看过上边的知识点,这道题现在已经可以轻松答对了 var a;// 函数a声明提前,块级作用域函数a的声明会被提升到全局作用域 var a = 0; // 已经声明了a,这里会忽略变量声明,直接赋值为0 if(true){ // 块级作用域 function a(){} // 函数a的定义,提升到块级作用域最前面 a = 10; // 执行a = 10,此时,在块级作用域中函数声明已经被提升到顶层,那么此时执行a,就是相当于赋值,将函数声明a赋值为数字a,这里就是赋值为10了, console.log(a,window.a); // a是块级作用域的function a,所以输出 10,window.a还是0,因为块级作用域函数只有执行函数声明语句的时候,才会重写对应的全局作用域上的同名变量 function a(){} // 执行到函数声明语句,虽然这一行代码是函数声明语句,但是a,已经为数字10了,所以,执行function a(){}之后,a的值10就会被赋值给全局作用域上的a,所以下面打印的window.a,a都为10 console.log(a,window.a); // a 还是块级作用域中的function a,前边已经被赋值为10,所以window.a前边已经变为了10 a = 20; // 仍然是函数定义块级作用域的a,重置为21 console.log(a,window.a); // 输出为函数提升的块级作用域的a,和window.a,所以这里输出20 10 } console.log(a); // 因为在块级作用域中window.a被改变成了10,所以这里现在是10 // 写出打印结果 var foo = 1; function bar() { // foo会声明提前 var foo; // !foo 等价于!undefined true if (!foo) { var foo = 10; } console.log(foo); // 10 } bar(); // 写出打印结果 var a = 1; function b() { // 函数声明提前 // var a = function a(){}; a = 10; // 赋值相当于是给函数a进行重新赋值,并且这是函数作用域,不是块级作用域 return; function a() {} } b(); console.log(a); // 1 # 2023/04/10 1. 能否以某种方式为下面的语句使用展开运算而不导致类型错误 ``` var obj = { x: 1, y: 2, z: 3 }; [...obj]; // TypeError // 能否以某种方式为上面的语句使用展开运算而不导致类型错误 // 如果可以,写出解决方式 ``` 2. 答案 展开语法和for-of 语句遍历iterabl对象定义要遍历的数据。Arrary和Map 是具有默认迭代行为的内置迭代器。对象不是可迭代的,但是可以通过使用iterable和iterator协议使它们可迭代。 在Mozilla文档中,如果一个对象实现了@iterator方法,那么它就是可迭代的,这意味着这个对象(或者它原型链上的一个对象)必须有一个带有@iterator键的属性,这个键可以通过常量Symbol.iterator获得。 // 解决方式一 var obj = { x: 1, y: 2, z: 3 }; obj[Symbol.iterator] = function(){ // iterator 是一个具有 next 方法的对象, // 它的返回至少有一个对象 // 两个属性:value&done。 return { // 返回一个 iterator 对象 next: function () { if (this._countDown === 3) { const lastValue = this._countDown; return { value: this._countDown, done: true }; } this._countDown = this._countDown + 1; return { value: this._countDown, done: false }; }, _countDown: 0, }; }; [...obj]; // 解决方式二 // 还可以使用 generator 函数来定制对象的迭代行为: var obj = { x: 1, y: 2, z: 3 }; obj[Symbol.iterator] = function*() { yield 1; yield 2; yield 3; }; [...obj]; // 打印 [1, 2, 3] # 2023/04/11 1. 请你完成一个 safeGet 函数,可以安全的获取无限多层次的数据 ``` // 请你完成一个safeGet函数,可以安全的获取无限多层次的数据,一旦数据不存在不会报错,会返回 undefined,例如 var data = { a: { b: { c: "yideng" } } }; safeGet(data, "a.b.c"); // => yideng safeGet(data, "a.b.c.d"); // => undefined safeGet(data, "a.b.c.d.e.f.g"); // => undefined ``` 2. 答案 ``` const safeGet = (o,path) => { try { return path.split('.').reduce((o,k) => o[k],o) } catch (error) { return undefined } } ``` # 2023/04/12 1. 写一个 isPrime()函数 ``` 写一个isPrime()函数,当其为质数时返回true,否则返回false。 提示:质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。 ``` 2. 答案 ``` function isPrime(number) { // If your browser doesn't support the method Number.isInteger of ECMAScript 6, // you can implement your own pretty easily if (typeof number !== 'number' || !Number.isInteger(number)) { // Alternatively you can throw an error. return false; } if (number < 2) { return false; } if (number === 2) { return true; } else if (number % 2 === 0) { return false; } var squareRoot = Math.sqrt(number); for(var i = 3; i <= squareRoot; i += 2) { if (number % i === 0) { return false; } } return true; } ``` 3. 解析 是面试中最常见的问题之一。然而,尽管这个问题经常出现并且也很简单,但是从被面试人提供的答案中能很好地看出被面试人的数学和算法水平。 首先, 因为JavaScript不同于C或者Java,因此你不能信任传递来的数据类型。如果面试官没有明确地告诉你,你应该询问他是否需要做输入检查,还是不进行检查直接写函数。严格上说,应该对函数的输入进行检查。 第二点要记住:负数不是质数。同样的,1和0也不是,因此,首先测试这些数字。此外,2是质数中唯一的偶数。没有必要用一个循环来验证4,6,8。再则,如果一个数字不能被2整除,那么它不能被4,6,8等整除。因此,你的循环必须跳过这些数字。如果你测试输入偶数,你的算法将慢2倍(你测试双倍数字)。可以采取其他一些更明智的优化手段,我这里采用的是适用于大多数情况的。例如,如果一个数字不能被5整除,它也不会被5的倍数整除。所以,没有必要检测10,15,20等等。如果你深入了解这个问题的解决方案,建议去看相关的Wikipedia介绍。 最后一点,你不需要检查比输入数字的开方还要大的数字。一般会遗漏掉这一点,并且也不会因为此而获得消极的反馈。但是,展示出这一方面的知识会给你额外加分。 # 2023/04/14 1. 请实现一个 flattenDeep 函数,把嵌套的数组扁平化~~ ``` flattenDeep([1, [2, [3, [4]], 5]]); //[1, 2, 3, 4, 5] // 请实现一个flattenDeep函数,把嵌套的数组扁平化 ``` 2. 答案 + 参考答案一:利用Array.prototype.flat ES6 为数组实例新增了 flat方法,用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数组没有影响。 flat默认只会 “拉平” 一层,如果想要 “拉平” 多层的嵌套数组,需要给 flat 传递一个整数,表示想要拉平的层数。 function flattenDeep(arr, deepLength) { return arr.flat(deepLength); } console.log(flattenDeep([1, [2, [3, [4]], 5]], 3)); 当传递的整数大于数组嵌套的层数时,会将数组拉平为一维数组,JS能表示的最大数字为 Math.pow(2, 53) - 1,因此我们可以这样定义 flattenDeep 函数 function flattenDeep(arr) { //当然,大多时候我们并不会有这么多层级的嵌套 return arr.flat(Math.pow(2,53) - 1); } console.log(flattenDeep([1, [2, [3, [4]], 5]])); + 参考答案二:利用 reduce 和 concat function flattenDeep(arr){ return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []); } console.log(flattenDeep([1, [2, [3, [4]], 5]])); + 参考答案三:使用 stack 无限反嵌套多层嵌套数组 function flattenDeep(input) { const stack = [...input]; const res = []; while (stack.length) { // 使用 pop 从 stack 中取出并移除值 const next = stack.pop(); if (Array.isArray(next)) { // 使用 push 送回内层数组中的元素,不会改动原始输入 original input stack.push(...next); } else { res.push(next); } } // 使用 reverse 恢复原数组的顺序 return res.reverse(); } console.log(flattenDeep([1, [2, [3, [4]], 5]])); # 2023/04/17 1. 请实现一个 uniq 函数,实现数组去重~~ ``` uniq([1, 2, 3, 5, 3, 2]);//[1, 2, 3, 5] // 请实现一个 uniq 函数,实现数组去重 ``` 2. 答案解析 + 参考答案一:利用ES6新增数据类型 Set Set类似于数组,但是成员的值都是唯一的,没有重复的值。 function uniq(arry) { return [...new Set(arry)]; } + 参考答案二:利用 indexOf function uniq(arry) { var result = []; for (var i = 0; i < arry.length; i++) { if (result.indexOf(arry[i]) === -1) { //如 result 中没有 arry[i],则添加到数组中 result.push(arry[i]) } } return result; } + 参考答案三:利用 includes function uniq(arry) { var result = []; for (var i = 0; i < arry.length; i++) { if (!result.includes(arry[i])) { //如 result 中没有 arry[i],则添加到数组中 result.push(arry[i]) } } return result; } + 参考答案四:利用 reduce function uniq(arry) { return arry.reduce((prev, cur) => prev.includes(cur) ? prev : [...prev, cur], []); } + 参考答案五:利用 Map function uniq(arry) { let map = new Map(); let result = new Array(); for (let i = 0; i < arry.length; i++) { if (map.has(arry[i])) { map.set(arry[i], true); } else { map.set(arry[i], false); result.push(arry[i]); } } return result; } # 2023/04/19 1. 实现 (5).add(3).minus(2) 功能 ``` // 实现 (5).add(3).minus(2) 功能 console.log((5).add(3).minus(2)); // 6 ``` 2. 答案解析 ``` Number.prototype.add = function (number) { if (typeof number !== 'number') { throw new Error('请输入数字~'); } return this + number; }; Number.prototype.minus = function (number) { if (typeof number !== 'number') { throw new Error('请输入数字~'); } return this - number; }; console.log((5).add(3).minus(2)); ``` 最后修改:2023 年 04 月 19 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏