前端经典面试题(60道前端面试题包含JSCSSReact浏览器等)
简答题1、什么是防抖和节流?有什么区别?如何实现?参考答案
防抖
触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间 思路:
每次触发事件时都取消之前的延时调用方法 function debounce(fn) { let timeout = null; // 创建一个标记用来存放定时器的返回值 return function () { clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉 timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数 fn.apply(this, arguments); }, 500); }; } function sayHi() { console.log("防抖成功"); } var inp = document.getElementById("inp"); inp.addEventListener("input", debounce(sayHi)); // 防抖
节流
高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率 思路:
每次触发事件时都判断当前是否有等待执行的延时函数 function throttle(fn) { let canRun = true; // 通过闭包保存一个标记 return function () { if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return canRun = false; // 立即设置为false setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中 fn.apply(this, arguments); // 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉 canRun = true; }, 500); }; } function sayHi(e) { console.log(e.target.innerWidth, e.target.innerHeight); } window.addEventListener("resize", throttle(sayHi)); 2、 get请求传参长度的误区、get和post请求在缓存方面的区别
误区:我们经常说get请求参数的大小存在限制,而post请求的参数大小是无限制的。 参考答案
实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get请求参数的限制是来源与浏览器或web服务器,浏览器或web服务器限制了url的长度。为了明确这个概念,我们必须再次强调下面几点: HTTP 协议 未规定 GET 和POST的长度限制 GET的最大长度显示是因为 浏览器和 web服务器限制了 URI的长度 不同的浏览器和WEB服务器,限制的最大长度不一样 要支持IE,则最大长度为2083byte,若只支持Chrome,则最大长度 8182byte
补充补充一个get和post在缓存方面的区别: get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。 post不同,post做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适合于请求缓存。 3、模块化发展历程
可从IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module、 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
把 DOM 离线后修改,比如:先把 DOM 给 display:none (有一次 Reflow),然后你修改100次,然后再把它显示出来
不要把 DOM 结点的属性值放在一个循环里当成循环里的变量for(let i = 0; i < 1000; i++) {// 获取 offsetTop 会导致回流,因为需要去获取正确的值console.log(document.querySelector(".test").style.offsetTop)} 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame CSS 选择符从右往左匹配查找,避免 DOM 深度过深 将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素。比如对于 video 标签,浏览器会自动将该节点变为图层。 react、Vue1、写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?参考答案
vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。在vue的diff函数中(建议先了解一下diff算法过程)。在交叉对比中,当新节点跟旧节点 头尾交叉对比 没有结果时,会根据新节点的key去对比旧节点数组中的key,从而找到相应旧节点(这里对应的是一个key => index 的map映射)。如果没找到就认为是一个新增节点。而如果没有key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个map映射,另一种是遍历查找。相比而言。map映射的速度更快。vue部分源码如下: // vue项目 src/core/vdom/patch.js -488行 // 以下是为了阅读性进行格式化后的代码 // oldCh 是一个旧虚拟节点数组 if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) } if(isDef(newStartVnode.key)) { // map 方式获取 idxInOld = oldKeyToIdx[newStartVnode.key] } else { // 遍历方式获取 idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) }
创建map函数 function createKeyToOldIdx (children, beginIdx, endIdx) { let i, key const map = {} for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key if (isDef(key)) map[key] = i } return map }
遍历寻找 // sameVnode 是对比新旧节点是否相同的函数 function findIdxInOld (node, oldCh, start, end) { for (let i = start; i < end; i++) { const c = oldCh[i] if (isDef(c) && sameVnode(node, c)) return i } } 2、React 中 setState 什么时候是同步的,什么时候是异步的?参考答案
在React中, 如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state 。所谓"除此之外",指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。
**原因:**在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是, 有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state 。 3、下面输出什么class Example extends React.Component { constructor() { super(); this.state = { val: 0 }; } componentDidMount() { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 1 次 log this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 2 次 log setTimeout(() => { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 3 次 log this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 4 次 log }, 0); } render() { return null; } }; 1、第一次和第二次都是在 react 自身生命周期内,触发时 isBatchingUpdates 为 true,所以并不会直接执行更新 state,而是加入了 dirtyComponents,所以打印时获取的都是更新前的状态 0。 2、两次 setState 时,获取到 this.state.val 都是 0,所以执行时都是将 0 设置成 1,在 react 内部会被合并掉,只执行一次。设置完成后 state.val 值为 1。 3、setTimeout 中的代码,触发时 isBatchingUpdates 为 false,所以能够直接进行更新,所以连着输出 2,3。 输出: 0 0 2 3 4、为什么虚拟dom会提高性能?参考答案
虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。
具体实现步骤如下:
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。 css1、分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景参考答案
结构:display:none: 会让元素完全从渲染树中消失,渲染的时候不占据任何空间, 不能点击, visibility: hidden:不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,不能点击 opacity: 0: 不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见,可以点击
继承:display: none:是非继承属性,子孙节点消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示。visibility: hidden:是继承属性,子孙节点消失由于继承了hidden,通过设置visibility: visible;可以让子孙节点显式。
性能:displaynone : 修改元素会造成文档回流,读屏器不会读取display: none元素内容,性能消耗较大 visibility:hidden: 修改元素只会造成本元素的重绘,性能消耗较少读屏器读取visibility: hidden元素内容 opacity: 0 :修改元素会造成重绘,性能消耗较少
联系:它们都能让元素不可见 2、清除浮动的方式有哪些?比较好的是哪一种?参考答案
常用的一般为三种 .clearfix , clear:both , overflow:hidden ;
比较好是 .clearfix ,伪元素万金油版本,后两者有局限性. .clearfix:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } .clearfix:before, .clearfix:after { content:""; display:table; } .clearfix:after{ clear:both; overflow:hidden; } .clearfix{ zoom:1; }
clear:both :若是用在同一个容器内相邻元素上,那是贼好的,有时候在容器外就有些问题了, 比如相邻容器的包裹层元素塌陷
overflow:hidden :这种若是用在同个容器内,可以形成 BFC 避免浮动造成的元素塌陷 4、css sprite 是什么,有什么优缺点参考答案
概念:将多个小图片拼接到一个图片中。通过 background-position 和元素尺寸调节需要显示的背景图案。
优点: 减少 HTTP 请求数,极大地提高页面加载速度 增加图片信息重复度,提高压缩比,减少图片大小 更换风格方便,只需在一张或几张图片上修改颜色或样式即可实现
缺点: 图片合并麻烦 维护麻烦,修改一个图片可能需要重新布局整个图片,样式 5、link与@import的区别参考答案 link 是 HTML 方式, @import 是 CSS 方式 link 最大限度支持并行下载, @import 过多嵌套导致串行下载,出现FOUC link 可以通过 rel="alternate stylesheet" 指定候选样式 浏览器对 link 支持早于 @import ,可以使用 @import 对老浏览器隐藏样式 @import 必须在样式规则之前,可以在 css 文件中引用其他文件 总体来说: link 优于@import 6、display: block;和display: inline;的区别参考答案
block 元素特点:
1.处于常规流中时,如果 width 没有设置,会自动填充满父容器 2.可以应用 margin/padding 3.在没有设置高度的情况下会扩展高度以包含常规流中的子元素 4.处于常规流中时布局时在前后元素位置之间(独占一个水平空间) 5.忽略 vertical-align
inline 元素特点
1.水平方向上根据 direction 依次布局
2.不会在元素前后进行换行
3.受 white-space 控制
4. margin/padding 在竖直方向上无效,水平方向上有效
5. width/height 属性对非替换行内元素无效,宽度由元素内容决定
6.非替换行内元素的行框高由 line-height 确定,替换行内元素的行框高由 height , margin , padding , border 决定 7.浮动或绝对定位时会转换为 block 8. vertical-align 属性生效 7、容器包含若干浮动元素时如何清理浮动参考答案 容器元素闭合标签前添加额外元素并设置 clear: both 父元素触发块级格式化上下文(见块级可视化上下文部分) 设置容器元素伪元素进行清理推荐的清理浮动方法 /** * 在标准浏览器下使用 * 1 content内容为空格用于修复opera下文档中出现 * contenteditable属性时在清理浮动元素上下的空白 * 2 使用display使用table而不是block:可以防止容器和 * 子元素top-margin折叠,这样能使清理效果与BFC,IE6/7 * zoom: 1;一致 **/ .clearfix:before, .clearfix:after { content: " "; /* 1 */ display: table; /* 2 */ } .clearfix:after { clear: both; } /** * IE 6/7下使用 * 通过触发hasLayout实现包含浮动 **/ .clearfix { *zoom: 1; } 8、PNG,GIF,JPG 的区别及如何选参考答案
GIF : 8 位像素,256 色 无损压缩 支持简单动画 支持 boolean 透明 适合简单动画
JPEG : 颜色限于 256 有损压缩 可控制压缩质量 不支持透明 适合照片
PNG : 有 PNG8 和 truecolor PNG PNG8 类似 GIF 颜色上限为 256,文件小,支持 alpha 透明度,无动画 适合图标、背景、按钮 9、display,float,position 的关系参考答案 如果 display 为 none,那么 position 和 float 都不起作用,这种情况下元素不产生框 否则,如果 position 值为 absolute 或者 fixed,框就是绝对定位的,float 的计算值为 none,display 根据下面的表格进行调整。 否则,如果 float 不是 none,框是浮动的,display 根据下表进行调整 否则,如果元素是根元素,display 根据下表进行调整 其他情况下 display 的值为指定值 总结起来: 绝对定位、浮动、根元素都需要调整display 10、如何水平居中一个元素参考答案 如果需要居中的元素为 常规流中 inline 元素 ,为父元素设置 text-align: center; 即可实现 如果需要居中的元素为 常规流中 block 元素 ,1)为元素设置宽度,2)设置左右 margin 为 auto。3)IE6 下需在父元素上设置 text-align: center; ,再给子元素恢复需要的值
aaaaaa aaaaaa a a a a a a a a 如果需要居中的元素为 浮动元素 ,1)为元素设置宽度,2) position: relative; ,3)浮动方向偏移量(left 或者 right)设置为 50%,4)浮动方向上的 margin 设置为元素宽度一半乘以-1
aaaaaa aaaaaa a a a a a a a a 如果需要居中的元素为 绝对定位元素 ,1)为元素设置宽度,2)偏移量设置为 50%,3)偏移方向外边距设置为元素宽度一半乘以-1
aaaaaa aaaaaa a a a a a a a a 如果需要居中的元素为 绝对定位元素 ,1)为元素设置宽度,2)设置左右偏移量都为 0,3)设置左右外边距都为 auto
aaaaaa aaaaaa a a a a a a a a JavaScript1、JS有几种数据类型,其中基本数据类型有哪些?参考答案
七种数据类型 Boolean Null Undefined Number String Symbol (ECMAScript 6 新定义) Object
(ES6之前)其中5种为基本类型: string , number , boolean , null , undefined ,
ES6出来的 Symbol 也是原始数据类型 ,表示独一无二的值
Object 为引用类型(范围挺大),也包括数组、函数, 2、Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?参考答案 const promise = new Promise((resolve, reject) => { console.log(1) resolve() console.log(2) }) promise.then(() => { console.log(3) }) console.log(4)
输出结果是: 1 2 4 3 promise构造函数是同步执行的,then方法是异步执行的 Promise new的时候会立即执行里面的代码 then是微任务 会在本次任务执行完的时候执行 setTimeout是宏任务 会在下次任务执行的时候执行 3、JS的四种设计模式参考答案
工厂模式
简单的工厂模式可以理解为解决多个相似的问题; function CreatePerson(name,age,sex) { var obj = new Object(); obj.name = name; obj.age = age; obj.sex = sex; obj.sayName = function(){ return this.name; } return obj; } var p1 = new CreatePerson("longen","28","男"); var p2 = new CreatePerson("tugenhua","27","女"); console.log(p1.name); // longen console.log(p1.age); // 28 console.log(p1.sex); // 男 console.log(p1.sayName()); // longen console.log(p2.name); // tugenhua console.log(p2.age); // 27 console.log(p2.sex); // 女 console.log(p2.sayName()); // tugenhua
单例模式
只能被实例化(构造函数给实例添加属性与方法)一次 // 单体模式 var Singleton = function(name){ this.name = name; }; Singleton.prototype.getName = function(){ return this.name; } // 获取实例对象 var getInstance = (function() { var instance = null; return function(name) { if(!instance) {//相当于一个一次性阀门,只能实例化一次 instance = new Singleton(name); } return instance; } })(); // 测试单体模式的实例,所以a===b var a = getInstance("aa"); var b = getInstance("bb");
沙箱模式
将一些函数放到自执行函数里面,但要用闭包暴露接口,用变量接收暴露的接口,再调用里面的值,否则无法使用里面的值 let sandboxModel=(function(){ function sayName(){}; function sayAge(){}; return{ sayName:sayName, sayAge:sayAge } })()
发布者订阅模式
就例如如我们关注了某一个公众号,然后他对应的有新的消息就会给你推送, //发布者与订阅模式 var shoeObj = {}; // 定义发布者 shoeObj.list = []; // 缓存列表 存放订阅者回调函数 // 增加订阅者 shoeObj.listen = function(fn) { shoeObj.list.push(fn); // 订阅消息添加到缓存列表 } // 发布消息 shoeObj.trigger = function() { for (var i = 0, fn; fn = this.list[i++];) { fn.apply(this, arguments);//第一个参数只是改变fn的this, } } // 小红订阅如下消息 shoeObj.listen(function(color, size) { console.log("颜色是:" + color); console.log("尺码是:" + size); }); // 小花订阅如下消息 shoeObj.listen(function(color, size) { console.log("再次打印颜色是:" + color); console.log("再次打印尺码是:" + size); }); shoeObj.trigger("红色", 40); shoeObj.trigger("黑色", 42);
代码实现逻辑是用数组存贮订阅者, 发布者回调函数里面通知的方式是遍历订阅者数组,并将发布者内容传入订阅者数组 4、列举出集中创建实例的方法参考答案
1.字面量 let obj={"name":"张三"}
2.Object构造函数创建 let Obj=new Object() Obj.name="张三"
3.使用工厂模式创建对象 function createPerson(name){ var o = new Object(); o.name = name; }; return o; } var person1 = createPerson("张三");
4.使用构造函数创建对象 function Person(name){ this.name = name; } var person1 = new Person("张三"); 5、简述一下前端事件流参考答案
HTML中与javascript交互是通过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件onscroll等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下"事件流"的概念。
什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2级事件流包括下面几个阶段。 事件捕获阶段 处于目标阶段 事件冒泡阶段
addEventListener : addEventListener 是DOM2 级事件新增的指定事件处理程序的操作,这个方法接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。
IE只支持事件冒泡 。 6、Function._proto_(getPrototypeOf)是什么?参考答案
获取一个对象的原型,在chrome中可以通过__proto__的形式,或者在ES6中可以通过Object.getPrototypeOf的形式。
那么Function.proto是什么么?也就是说Function由什么对象继承而来,我们来做如下判别。 Function.__proto__==Object.prototype //false Function.__proto__==Function.prototype//true
我们发现Function的原型也是Function。
我们用图可以来明确这个关系:
7、简述一下原型 / 构造函数 / 实例参考答案 原型 (prototype) : 一个简单的对象,用于实现对象的 属性继承 。可以简单的理解成对象的爹。在 Firefox 和 Chrome 中,每个 JavaScript 对象中都包含一个 __proto__ (非标准)的属性指向它爹(该对象的原型),可 obj.__proto__ 进行访问。 构造函数: 可以通过 new 来 新建一个对象 的函数。 实例: 通过构造函数和 new 创建出来的对象,便是实例。 实例通过__proto__指向原型,通过constructor指向构造函数 。
这里来举个栗子,以 Object 为例,我们常用的 Object 便是一个构造函数,因此我们可以通过它构建实例。 // 实例 const instance = new Object()
则此时, 实例为instance , 构造函数为Object ,我们知道,构造函数拥有一个 prototype 的属性指向原型,因此原型为: // 原型 const prototype = Object.prototype
这里我们可以来看出三者的关系: 实例.__proto__ === 原型 原型.constructor === 构造函数 构造函数.prototype === 原型 // 这条线其实是是基于原型进行获取的,可以理解成一条基于原型的映射线 // 例如: // const o = new Object() // o.constructor === Object --> true // o.__proto__ = null; // o.constructor === Object --> false 实例.constructor === 构造函数 8、简述一下JS继承,并举例参考答案
在 JS 中,继承通常指的便是 原型链继承 ,也就是通过指定原型,并可以通过原型链继承原型上的属性或者方法。 最优化: 圣杯模式
var inherit = (function(c,p){var F = function(){};return function(c,p){F.prototype = p.prototype;c.prototype = new F();c.uber = p.prototype;c.prototype.constructor = c;}})(); 使用 ES6 的语法糖 class / extends 9、函数柯里化参考答案
在函数式编程中,函数是一等公民。那么函数柯里化是怎样的呢?
函数柯里化指的是将能够接收多个参数的函数转化为接收单一参数的函数,并且返回接收余下参数且返回结果的新函数的技术。
函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。
在一个函数中,首先填充几个参数,然后再返回一个新的函数的技术,称为函数的柯里化。通常可用于在不侵入函数的前提下,为函数 预置通用参数 ,供多次重复调用。 const add = function add(x) { return function (y) { return x + y } } const add1 = add(1) add1(2) === 3 add1(20) === 21 10、说说bind、call、apply 区别?参考答案
call 和 apply 都是为了解决改变 this 的指向。作用都是相同的,只是传参的方式不同。
除了第一个参数外, call 可以接收一个参数列表, apply 只接受一个参数数组。 let a = { value: 1 } function getValue(name, age) { console.log(name) console.log(age) console.log(this.value) } getValue.call(a, "yck", "24") getValue.apply(a, ["yck", "24"])
bind 和其他两个方法作用也是一致的,只是该方法会返回一个函数。并且我们可以通过 bind 实现柯里化。 (下面是对这三个方法的扩展介绍)
如何实现一个 bind 函数
对于实现以下几个函数,可以从几个方面思考 不传入第一个参数,那么默认为 window 改变了 this 指向,让新的对象可以执行该函数。那么思路是否可以变成给新的对象添加一个函数,然后在执行完以后删除? Function.prototype.myBind = function (context) { if (typeof this !== "function") { throw new TypeError("Error") } var _this = this var args = [...arguments].slice(1) // 返回一个函数 return function F() { // 因为返回了一个函数,我们可以 new F(),所以需要判断 if (this instanceof F) { return new _this(...args, ...arguments) } return _this.apply(context, args.concat(...arguments)) } }
如何实现一个call函数 Function.prototype.myCall = function (context) { var context = context || window // 给 context 添加一个属性 // getValue.call(a, "yck", "24") => a.fn = getValue context.fn = this // 将 context 后面的参数取出来 var args = [...arguments].slice(1) // getValue.call(a, "yck", "24") => a.fn("yck", "24") var result = context.fn(...args) // 删除 fn delete context.fn return result }
如何实现一个apply函数 Function.prototype.myApply = function (context) { var context = context || window context.fn = this var result // 需要判断是否存储第二个参数 // 如果存在,就将第二个参数展开 if (arguments[1]) { result = context.fn(...arguments[1]) } else { result = context.fn() } delete context.fn return result } 11、箭头函数的特点参考答案 function a() { return () => { return () => { console.log(this) } } } console.log(a()()())
箭头函数其实是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this 。在这个例子中,因为调用 a 符合前面代码中的第一个情况,所以 this 是 window 。并且 this 一旦绑定了上下文,就不会被任何代码改变。 程序阅读题1、下面程序输出的结果是什么?function sayHi() { console.log(name); console.log(age); var name = "Lydia"; let age = 21; } sayHi(); A: Lydia 和 undefined B: Lydia 和 ReferenceError C: ReferenceError 和 21 D: undefined 和 ReferenceError 参考答案
在函数中,我们首先使用 var 关键字声明了 name 变量。这意味着变量在创建阶段会被提升( JavaScript 会在创建变量创建阶段为其分配内存空间),默认值为 undefined ,直到我们实际执行到使用该变量的行。我们还没有为 name 变量赋值,所以它仍然保持 undefined 的值。
使用 let 关键字(和 const )声明的变量也会存在变量提升,但与 var 不同,初始化没有被提升。在我们声明(初始化)它们之前,它们是不可访问的。这被称为"暂时死区"。当我们在声明变量之前尝试访问变量时, JavaScript 会抛出一个 ReferenceError 。
关于 let 的是否存在变量提升,我们何以用下面的例子来验证: let name = "ConardLi" { console.log(name) // Uncaught ReferenceError: name is not defined let name = "code秘密花园" }
let 变量如果不存在变量提升, console.log(name) 就会输出 ConardLi ,结果却抛出了 ReferenceError ,那么这很好的说明了, let 也存在变量提升,但是它存在一个"暂时死区",在变量未初始化或赋值前不允许访问。
变量的赋值可以分为三个阶段: 创建变量,在内存中开辟空间 初始化变量,将变量初始化为 undefined 真正赋值
关于 let 、 var 和 function : let 的「创建」过程被提升了,但是初始化没有提升。 var 的「创建」和「初始化」都被提升了。 function 的「创建」「初始化」和「赋值」都被提升了。 2、下面代码输出什么var a = 10; (function () { console.log(a) a = 5 console.log(window.a) var a = 20; console.log(a) })()
依次输出:undefined -> 10 -> 20 在立即执行函数中,var a = 20; 语句定义了一个局部变量 a,由于js的变量声明提升机制,局部变量a的声明会被提升至立即执行函数的函数体最上方,且由于这样的提升并不包括赋值,因此第一条打印语句会打印undefined,最后一条语句会打印20。 由于变量声明提升,a = 5; 这条语句执行时,局部的变量a已经声明,因此它产生的效果是对局部的变量a赋值,此时window.a 依旧是最开始赋值的10, 3、下面的输出结果是什么?class Chameleon { static colorChange(newColor) { this.newColor = newColor; } constructor({ newColor = "green" } = {}) { this.newColor = newColor; } } const freddie = new Chameleon({ newColor: "purple" }); freddie.colorChange("orange"); A: orange B: purple C: green D: TypeError
答案: D
colorChange 方法是静态的。静态方法仅在创建它们的构造函数中存在,并且不能传递给任何子级。由于 freddie 是一个子级对象,函数不会传递,所以在 freddie 实例上不存在 freddie 方法:抛出 TypeError 。 4、下面代码中什么时候会输出1?var a = ?; if(a == 1 && a == 2 && a == 3){ conso.log(1); } 参考答案
因为==会进行隐式类型转换 所以我们重写toString方法就可以了 var a = { i: 1, toString() { return a.i++; } } if( a == 1 && a == 2 && a == 3 ) { console.log(1); } 5、下面的输出结果是什么?var obj = { "2": 3, "3": 4, "length": 2, "splice": Array.prototype.splice, "push": Array.prototype.push } obj.push(1) obj.push(2) console.log(obj) 参考答案
1.使用第一次push,obj对象的push方法设置 obj[2]=1;obj.length+=1 2.使用第二次push,obj对象的push方法设置 obj[3]=2;obj.length+=1 3.使用console.log输出的时候,因为obj具有 length 属性和 splice 方法,故将其作为数组进行打印 4.打印时因为数组未设置下标为 0 1 处的值,故打印为empty,主动 obj[0] 获取为 undefined
6、下面代码输出的结果是什么?var a = {n: 1}; var b = a; a.x = a = {n: 2}; console.log(a.x) console.log(b.x) 参考答案
undefined {n:2}
首先,a和b同时引用了{n:2}对象,接着执行到a.x = a = {n:2}语句,尽管赋值是从右到左的没错,但是.的优先级比=要高,所以这里首先执行a.x,相当于为a(或者b)所指向的{n:1}对象新增了一个属性x,即此时对象将变为{n:1;x:undefined}。之后按正常情况,从右到左进行赋值,此时执行a ={n:2}的时候,a的引用改变,指向了新对象{n:2},而b依然指向的是旧对象。之后执行a.x = {n:2}的时候,并不会重新解析一遍a,而是沿用最初解析a.x时候的a,也即旧对象,故此时旧对象的x的值为{n:2},旧对象为 {n:1;x:{n:2}},它被b引用着。后面输出a.x的时候,又要解析a了,此时的a是指向新对象的a,而这个新对象是没有x属性的,故访问时输出undefined;而访问b.x的时候,将输出旧对象的x的值,即{n:2}。 7、下面代码的输出是什么?function checkAge(data) { if (data === { age: 18 }) { console.log("You are an adult!"); } else if (data == { age: 18 }) { console.log("You are still an adult."); } else { console.log(`Hmm.. You don"t have an age I guess`); } } checkAge({ age: 18 }); 参考答案
Hmm.. You don"t have an age I guess
在比较相等性,原始类型通过它们的值进行比较,而对象通过它们的引用进行比较。 JavaScript 检查对象是否具有对内存中相同位置的引用。
我们作为参数传递的对象和我们用于检查相等性的对象在内存中位于不同位置,所以它们的引用是不同的。
这就是为什么 { age: 18 } === { age: 18 } 和 { age: 18 } == { age: 18 } 返回 false 的原因。 8、下面代码的输出是什么?const obj = { 1: "a", 2: "b", 3: "c" }; const set = new Set([1, 2, 3, 4, 5]); obj.hasOwnProperty("1"); obj.hasOwnProperty(1); set.has("1"); set.has(1); 参考答案
true` `true` `false` `true
所有对象键(不包括 Symbols )都会被存储为字符串,即使你没有给定字符串类型的键。这就是为什么 obj.hasOwnProperty("1") 也返回 true 。
上面的说法不适用于 Set 。在我们的 Set 中没有 "1" : set.has("1") 返回 false 。它有数字类型 1 , set.has(1) 返回 true 。 9、下面代码的输出是什么?// example 1 var a={}, b="123", c=123; a[b]="b"; a[c]="c"; console.log(a[b]); --------------------- // example 2 var a={}, b=Symbol("123"), c=Symbol("123"); a[b]="b"; a[c]="c"; console.log(a[b]); --------------------- // example 3 var a={}, b={key:"123"}, c={key:"456"}; a[b]="b"; a[c]="c"; console.log(a[b]); 参考答案
这题考察的是对象的键名的转换。 对象的键名只能是字符串和 Symbol 类型。 其他类型的键名会被转换成字符串类型。 对象转字符串默认会调用 toString 方法。 // example 1 var a={}, b="123", c=123; a[b]="b"; // c 的键名会被转换成字符串"123",这里会把 b 覆盖掉。 a[c]="c"; // 输出 c console.log(a[b]); // example 2 var a={}, b=Symbol("123"), c=Symbol("123"); // b 是 Symbol 类型,不需要转换。 a[b]="b"; // c 是 Symbol 类型,不需要转换。任何一个 Symbol 类型的值都是不相等的,所以不会覆盖掉 b。 a[c]="c"; // 输出 b console.log(a[b]); // example 3 var a={}, b={key:"123"}, c={key:"456"}; // b 不是字符串也不是 Symbol 类型,需要转换成字符串。 // 对象类型会调用 toString 方法转换成字符串 [object Object]。 a[b]="b"; // c 不是字符串也不是 Symbol 类型,需要转换成字符串。 // 对象类型会调用 toString 方法转换成字符串 [object Object]。这里会把 b 覆盖掉。 a[c]="c"; // 输出 c console.log(a[b]); 10、下面代码的输出是什么?(() => { let x, y; try { throw new Error(); } catch (x) { (x = 1), (y = 2); console.log(x); } console.log(x); console.log(y); })(); 参考答案
1` `undefined` `2
catch 块接收参数 x 。当我们传递参数时,这与变量的 x 不同。这个变量 x 是属于 catch 作用域的。
之后,我们将这个块级作用域的变量设置为 1 ,并设置变量 y 的值。现在,我们打印块级作用域的变量 x ,它等于 1 。
在 catch 块之外, x 仍然是 undefined ,而 y 是 2 。当我们想在 catch 块之外的 console.log(x) 时,它返回 undefined ,而 y 返回 2 。 11、下面代码的输出结果是什么?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(); 参考答案
输出顺序是 4 2 1 function Foo() { Foo.a = function() { console.log(1) } this.a = function() { console.log(2) } } // 以上只是 Foo 的构建方法,没有产生实例,此刻也没有执行 Foo.prototype.a = function() { console.log(3) } // 现在在 Foo 上挂载了原型方法 a ,方法输出值为 3 Foo.a = function() { console.log(4) } // 现在在 Foo 上挂载了直接方法 a ,输出值为 4 Foo.a(); // 立刻执行了 Foo 上的 a 方法,也就是刚刚定义的,所以 // # 输出 4 let obj = new Foo(); /* 这里调用了 Foo 的构建方法。Foo 的构建方法主要做了两件事: 1. 将全局的 Foo 上的直接方法 a 替换为一个输出 1 的方法。 2. 在新对象上挂载直接方法 a ,输出值为 2。 */ obj.a(); // 因为有直接方法 a ,不需要去访问原型链,所以使用的是构建方法里所定义的 this.a, // # 输出 2 Foo.a(); // 构建方法里已经替换了全局 Foo 上的 a 方法,所以 // # 输出 1
学生手表哪个好,学生手表品牌推荐天美时手表还是不错的,非常好用,可参考下面对天美时手表试用点评和感受。天美时算是个手表老品牌了,在很多国家都能看到手表专柜,这次入手户外运动风的一款,整体是比较满意的,做工与材质上
vivoS15系列惊现王牌对王牌,该机也是年轻人的王牌团队vivoS系列即将推出新成员的消息已经在官博正式官宣,令人没想到的是vivoS15系列的真身竟然提前在综艺节目王牌对王牌中进行了亮相,嘉宾们手持vivoS15系列拍摄了诸多样张,向
vivo手机和小米手机,哪个质量更好?有些回复一来就说之前夸vo手机质量品控好的都是在捧vo来黑小米品牌有些偏激了些,因为人家很有可能是深有体验,而且在很早的时候小米手机的确是存在质量问题。随着这么多年都过去了,质量肯
华为小米不能随便买,这3款可以闭眼买也不吃亏,比苹果便宜身边有很多不懂手机的朋友换新机首先考虑的可能都是小米和华为,毕竟对其他品牌的手机不是那么的熟悉,但是华为小米手机也不能随便买,真正好评多,体验好的手机还是不多,学长进行了一个汇总,
如何做好科技服务工作,科技服务工作怎么做?国家正在大力发展科技服务工作,如今从中央到地方各级都在努力的希望做这个领域。科技服务产业也被称之为一个非常巨大的金矿。但是如何做,怎么做,是否有一些可以借鉴的案例。点通智库以科技创
空调维修收费标准有些朋友经常咨询格力售后关于空调维修方面的收费问题空调加氟多少钱,空调移机价格空调维修有上门费吗等等相关售后的法规已经出现,在一定程度上对家电类产品的售后维修有所规范。对于官方的客
抖音电商标准化选品指南想做好抖音直播带货,产品是核心,但大部分人不重视货品的重要性,而是绞尽脑汁的追求各种玩法和套路,最终都会因为供应链问题,死得很惨。要知道再多的运营玩法都不过是帮助产品链接到用户的手
3个方法教你彻底关闭电脑弹窗广告,简单又实用!建议收藏每天在使用电脑办公或浏览页面时,总会有令人厌烦的弹窗广告出现,每次都要手动去关闭它,等下一次开机还是会出现,今天就来教大家3个彻底关闭电脑弹窗广告的方法,让你不再烦恼!一任务管理器
父母想买华为手机支持国产,我强制给他们买了苹果手机,合适吗?很简单,不合适,不是因为爱国不爱国,而是你连最基本的尊重父母的意见都不知道,父母年龄大了,可能有些事情他们不一定是对的,但是只要不是特别过分的事情,晚辈还是尽量去尊重他们的意见,毕
加密货币崩盘?发生了什么?货币的本质,是它的信用。一样东西能否成为货币,只取决于人们是否相信它的价值,至于它是不是真的有价值,根本不重要。世界主要的法定货币,如美元,欧元,人民币,本质上是一张纸,但是背后的
我一个月三千块钱想买一万的功放机败家吗?这怎么能算败家,一月三千元虽然不算多,想买一万元的东西也不算贵,赚钱就是为自已服务!您好,非常高兴能回答您的问题。三千多工资买一万多大功放到底败不败家要从几方面来讲。第一,普通消费