HTML和CSS篇: 2024高频前端面试题 HTML 和 CSS 篇-CSDN博客
一. JavaScript篇
1. 数据类型有哪些
1) 基本数据类型
数值(Number)、字符串(String)、布尔值(Boolean)、Undefined、Null、Symbol、BigInt
2) 引用数据类型
对象(Object)、数组(Array)、函数(Function),还有两个特殊的对象:正则(RegExp)和日期(Date)
3) 存储区别
基本数据类型存储在栈中
引用类型的对象存储于堆中
2. 检测数据类型有哪些方法
1)typeof
除了对象、数组、null检测为object,其他可以正确检测其类型
console.log(typeof 2); // number console.log(typeof true); // boolean console.log(typeof 'str'); // string console.log(typeof function(){}); // function console.log(typeof undefined); // undefined console.log(typeof {}); // object console.log(typeof []); // object console.log(typeof null); // object
2)instanceof
只能正确判断引用数据类型,而不能判断基本数据类型 (返回的是布尔值)
console.log(2 instanceof Number); // false console.log(true instanceof Boolean); // false console.log('str' instanceof String); // false console.log([] instanceof Array); // true console.log(function(){} instanceof Function); // true console.log({} instanceof Object); // true
3)constructor
都可以正确判断其类型 (判断不了null、undefined)
console.log((2).constructor === Number); // true console.log((true).constructor === Boolean); // true console.log(('str').constructor === String); // true console.log(([]).constructor === Array); // true console.log((function() {}).constructor === Function); // true console.log(({}).constructor === Object); // true
4)Object.prototype.toString.call ( obj )
使用 Object 对象的原型方法 toString 来判断数据类型
let a = Object.prototype.toString; console.log(a.call(2)); console.log(a.call(true)); console.log(a.call('str')); console.log(a.call([])); console.log(a.call(function(){})); console.log(a.call({})); console.log(a.call(undefined)); console.log(a.call(null));
3. 操作数组会改变和不会改变原数组的方法有哪些
1) 改变原数组
添加元素类(返回新的长度):
push( ) 把元素添加到数组尾部
unshift( ) 在数组头部添加元素
删除元素类(返回的是被删除的元素):
pop( ) 移除数组最后一个元素
shift( ) 删除数组第一个元素
颠倒顺序:
reverse( ) 在原数组中颠倒元素的顺序
插入、删除、替换数组元素(返回被删除的数组):
splice(a, b, c…n)
a 代表要操作数组位置的索引值,必填
b 代表要删除元素的个数,必须是数字,可以是0,如果没填就是删除从a到数组的结尾
c…n 代表要添加到数组中的新值
排序:
sort( ) 对数组元素进行排序
2) 不改变原数组
concat() 连接两个或更多数组,返回结果
slice() 截取一段数组,返回新数组
join() 把数组的所有元素放到一个字符串
toString() 把数组转成字符串
indexOf() 搜索数组中的元素,并返回他所在的位置
filter() 挑选数组中符合条件的并返回符合要求的数组
every() 检测数组中每个元素是否都符合要求
some() 检测数组中是否有元素符合要求
4. 判断数组的方式有哪些方法
1)通过 Object.prototype.toString.call ( obj ) 判断
Object.prototype.toString.call(obj).slice(8,-1) === 'Array' //true Object.prototype.toString.call(obj).indexOf('Array') > -1 //true
2)instanceOf
console.log(arr instanceOf Array) //true
3)constructor
arr.constructor.toString( arr ).indexOf('Array') > -1 //true
4)通过ES6的 Array.isArray( arr ) 做判断
Array.isArray( arr ) //true
5)通过原型链做判断
arr.__proto__ === Array.prototype //true
6)通过Array.prototype.isPrototypeOf( obj )
Array.prototype.isPrototypeOf( obj ) //true
5. 遍历数组的方法有哪些
1)for循环 2)for…in 3)for…of 4)forEach
for in 和 for of 都可以循环数组,for in 输出的是数组的索引,而for of 输出的是数组的每一项的值
6. 遍历对象的方法有哪些
1)Object.keys( obj ) 2)for…in 3)Object.getOwnPropertyNames( obj )
for in遍历的是数组的索引,对象的属性,以及原型链上的属性
7. forEach和Map的区别
1)forEach 返回值为 undefined,会修改原数组,map 有返回值,会返回新的数组,不会修改原数组
2)map 返回的是新数组,可以进行链式调用,而 forEach 不能
3)map 的执行速度比forEach的快
8. null 和 undefined 的区别
null 和 undefined不能通过 == 来判断。
undefined
这个变量从根本上就没有定义
一般变量声明了但还没有定义的时候会返回 undefined,转化数值时为NaN
隐藏式 空值
null
这个值虽然定义了,但它并未指向任何内存中的对象
主要用于给一些可能会返回对象的变量赋值,作为初始化 ,转化数值时为 0
声明式 空值
9. “ ===”、“ ==” 的区别?
==:
如果操作数相等,则会返回 true
两个都为简单类型,字符串和布尔值都会转换成数值,再比较
简单类型与引用类型比较,对象转化成其原始类型的值,再比较
两个都为引用类型,则比较它们是否指向同一个对象
null 和 undefined 相等
存在 NaN 则返回 false
===:只有在无需类型转换运算数就相等的情况下,才返回 true,需要检查数据类型
区别:
相等操作符(==)会做类型转换,再进行值的比较,全等运算符不会做类型转换
10. 对作用域链的理解
一般情况使用的变量取值是在当前执行环境的作用域中查找,如果当前作用域没有查到这个值,就会向上级作用域查找,直到查找到全局作用域,这么一个查找的过程我们叫做作用域链
作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到
window
对象即被终止,作用域链向下访问变量是不被允许的简单的说,
作用域就是变量与函数的可访问范围
,即作用域控制着变量与函数的可见性和生命周期
11. 原型,原型链 ? 有什么特点?
1)原型:
JavaScript的所有对象中都包含了一个 [__proto__] 内部属性,这个属性所对应的就是该对象的原型
JavaScript的函数对象,除了原型 [__proto__] 之外,还预置了 prototype 属性
当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型 [__proto__]
2)原型链:
当一个对象调用的属性/方法自身不存在时,就会去自己 [__proto__] 关联的前辈 prototype 对象上去找
如果没找到,就会去该 prototype 原型 [__proto__] 关联的前辈 prototype 去找。依次类推,直到找到属性/方法或 undefined 为止。从而形成了所谓的“原型链”
3)原型特点:
JavaScript对象是通过引用来传递的,当修改原型时,与之相关的对象也会继承这一改变
12. 对闭包的理解
1)原理:
闭包就是一个有权访问另外一个函数作用域中变量的函数。
在本质上,闭包是将函数内部和函数外部连接起来的桥梁。一般就是一个函数A,return其内部的函数B, 被return出去的B函数能够在外部访问A函数内部的变量,这时候就形成了一个函数B的变量背包,这个变量背包在A函数外部只能通过B函数访问,并且函数A执行结束后这个变量背包也不会被销毁,所以垃圾回收机制不会将当前变量回收掉的,有可能会带来内存损耗( 解决:闭包不在使用时,要及时释放。将引用内层函数对象的变量赋值为null)。
2)为什么使用闭包
因为局部变量无法长久保存和共享,而全局变量会造成变量污染,所以需要一种机制既可以长久保存又不会全局污染,因而闭包就产生了,好处就是可以设计私有的方法和变量。
3)场景
setTimeout
原生的setTimeout传递的第一个函数不能传参,可以通过闭包的形式实现传参
function fun1(a) { return function fun2() { conlog.log(a) } } let fun = fun1(1) setTimeout(fun, 1000) // 输出1
防抖&节流
节流: 如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函数,不管在这个中间有多少次触发这个事件, 执行函数的频次总是固定的; 防抖: 当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间(非常短的时间),当事件密集触发时, 函数的触发会被频繁的推迟,只有等待了一段时间也没有事件触发,才会真正的执行响应函数
封装私有变量
// 创建计时器 function fun1() { let sum = 0 let obj = { inc: function() { sum++ return sum } } return obj } let fun = fun1() console.log(fun.inc()) // 1 console.log(fun.inc()) // 2 console.log(fun.inc()) // 3
13. 宏任务 与 微任务
宏任务(macro - task)和微任务(micro - task)都是异步任务,宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。常见的有:
1) 宏任务:
script脚本的执行(全局任务)、setTimeout 、setInterval 、setImmediate、MessageChannel 、postMessage 、I/O 操作、UI 渲染(rendering)
2) 微任务:
promise 的回调( Promise.[ then/ catch/ finally ] )、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver(浏览器环境)
14. Js 事件循环的理解
前提: JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环
在JavaScript中,所有的任务都可以分为
- 同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
- 异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等
同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环
异步任务分为微任务和宏任务:
微任务: 一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
- Promise
- MutaionObserver 监听dom发生改变的
- Object.observe(已废弃;Proxy 对象替代)
- process.nextTick(Node.js)
宏任务: 宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
- script (可以理解为外层同步代码)
- setTimeout/setInterval
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
执行顺序:
- 先执行同步代码,
- 遇到异步宏任务则将异步宏任务放入宏任务队列中,
- 遇到异步微任务则将异步微任务放入微任务队列中,
- 当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,
- 微任务执行完毕后再将异步宏任务从队列中调入主线程执行,
- 一直循环直至所有任务执行完毕。
15. JS 延时加载的方式有哪些
async:js脚本下载和html解析是同步的,执行js脚本的时候html是停止解析的,不能保证加载的顺序(谁先加载完谁先执行)
defer:js脚本下载和html解析是同步的,等html全部解析完,才去执行js代码,按照顺序执行js脚本
动态创建DOM方式:
动态创建 DOM 标签的方式,可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本( 即document.createElement(‘script’)
让 js 最后加载 :将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行
使用setTimeout延迟方法:设置一个定时器来延迟加载js脚本文件
16. 深拷贝与浅拷贝
1) 深拷贝:
深拷贝直接拷贝对象到一块新的内存区域,然后把新对象的指针指向这块内存,新对象指向了新的地址
a) Json.parse 和 Json.stringfy 相互转换
//_tmp和result是相互独立的,没有任何联系,有各自的存储空间。 let deepClone = function (obj) { let _tmp = JSON.stringify(obj);//将对象转换为json字符串形式 let result = JSON.parse(_tmp);//将转换而来的字符串转换为原生js对象 return result; }; let obj1 = { gita: { age: 20, class: 1502 } }; let test = deepClone(obj1); console.log(test); //test:{gita:{age:20, class:1502}} test.gita.age = 21; console.log(test); //test:{gita:{age:21, class:1502}} console.log(obj1); //obj1:{gita:{age:20, class:1502}} age 不被改变了
缺陷:
1、如果存在时间对象,时间对象变成了字符串。
2、如果有RegExp、Error 对象,则序列化的结果将只得到空对象。
3、如果有函数,undefined,则序列化的结果会把函数, undefined 丢失。
4、如果有NaN、Infinity 和-Infinity,则序列化的结果会变成null。
5、JSON.stringify() 只能序列化对象可枚举的自有属性。如果obj 中的对象是有构造函数生成的, 则使用JSON.parse( JSON.stringify(obj) ) 深拷贝后,会丢弃对象的constructor。
6、如果对象中存在循环引用的情况也无法正确实现深拷贝。
b) 递归方法
function deepClone(obj) { let newObj = Array.isArray(obj) ? [] : {}; if (obj && typeof obj === 'object') { for(let key in obj){ if (obj[key] && typeof obj[key] === 'object'){ //判断对象的这条属性是否为对象 newObj[key] = deepClone(obj[key]); //若是对象进行嵌套调用 }else{ newObj[key] = obj[key] } } return newObj; //返回深度克隆后的对象 }else { return obj } } // 原对象 var obj = {a:1,b:2}; // 创建深拷贝对象 var OBJ = deepClone(obj);
2) 浅拷贝:
浅拷贝并不拷贝对象本身,只是对指向对象的指针进行拷贝,复制的对象和原对象都指向同一个地址
a) Object.assign
let outObj = { inObj: {a: 1, b: 2} } let newObj = Object.assign( {}, outObj ) newObj.inObj.a = 2 console.log(outObj) // {inObj: {a: 2, b: 2}} <!-- Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。 然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。 -->
b) 扩展运算符
let outObj = { inObj: {a: 1, b: 2} } let newObj = {...outObj} newObj.inObj.a = 2 console.log(outObj) // {inObj: {a: 2, b: 2}} <!-- 扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。 它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。 -->
17. 事件模型
W3C中定义事件的发生经历三个阶段:捕获阶段(capturing)、目标阶段(targetin)、冒泡阶段(bubbling)
冒泡型事件:当你使用事件冒泡时,子级元素先触发,父级元素后触发
捕获型事件:当你使用事件捕获时,父级元素先触发,子级元素后触发
DOM事件流:同时支持两种事件模型:捕获型事件和冒泡型事件
阻止冒泡:在W3c中,使用stopPropagation()方法;在IE下设置cancelBubble = true
阻止捕获:阻止事件的默认行为,例如click - 后的跳转。在W3c中,使用preventDefault()方法,在IE下设置window.event.returnValue = false
18. 什么是事件代理
事件代理(Event Delegation),又称之为事件委托。是 JavaScript 中常用绑定事件的常用技巧。
顾名思义,“事件代理”即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。
使用事件代理的好处是可以提高性能。
可以大量节省内存占用,减少事件注册,比如在table上代理所有td的click事件就非常棒
可以实现当新增子对象时无需再次对其绑定
19. Ajax原理,ajax优缺点?
Ajax的原理简单来说是在用户和服务器之间加了—个中间层(AJAX引擎),通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据
Ajax的过程只涉及JavaScript、XMLHttpRequest和DOM。
XMLHttpRequest是ajax的核心机制
优点:
通过异步模式,提升了用户体验.
优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用.
Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载。
Ajax可以实现动态不刷新(局部刷新)
缺点:
安全问题 AJAX暴露了与服务器交互的细节。
对搜索引擎的支持比较弱。
不容易调试。
20. ajax过程?
- 创建XMLHttpRequest对象,也就是创建一个异步调用对象.
- 创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.
- 设置响应HTTP请求状态变化的函数.
- 发送HTTP请求.
- 获取异步调用返回的数据.
- 使用JavaScript和DOM实现局部刷新.
21. 用js递归的方式写1到100求和?
function add(num1, num2) { const num = num1 + num2; if(num2 === 100) { return num; } else { return add(num, num2 + 1) } } var sum = add(1, 2);
22. 说说 Javascript 数字精度丢失的问题,如何解决?
例子:0.1+0.2===0.3 =>false 涉及IEE754标准
问题原因:
计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法
因为存储时有位数限制(64位),并且某些十进制的浮点数在转换为二进制数时会出现无限循环,会造成二进制的舍入操作(0舍1入),当再转换为十进制时就造成了计算误差解决:
使用 toFixed() 方法:将浮点数转化为一个指定位数小数的字符串形式
使用第三方库,Math.js、BigDecimal.js
23. 如何判断一个对象为空对象
通过
Object.keys(obj)
方法获取对象的所有属性名,并判断属性数量是否为 0 来实现let obj = {'name':'zs'} Object.keys(obj).length //1 let objs = {} Object.keys(objs).length //0
24. DOM常见的操作有哪些
创建节点
createElement 创建新元素,接受一个参数,即要创建元素的标签名
查询节点
querySelector 传入任何有效的css 选择器,即可选中单个 DOM元素(首个) 如果页面上没有指定的元素时,返回 null
querySelectorAll 返回一个包含节点子树内所有与之相匹配的Element节点列表,如果没有相匹配的,则返回一个空节点列表
更新节点
innerHTML 不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树
innerText
style dom对象.style.样式属性 = ‘’
添加节点
innerHTML
appendChild 把一个子节点添加到父节点的最后一个子节点
insertBefore(新dom,指定dom对象) 把子节点插入到指定的位置的前面
setAttribute 在指定元素中添加一个属性节点,如果元素中已有该属性改变属性值
删除节点
removeChild 拿到父节点,要删除的节点dom对象。父.removeChild(子)
25. 说说你对BOM的理解,常见的BOM对象你了解哪些?
BOM (Browser Object Model),浏览器对象模型,提供了独立于内容与浏览器窗口进行交互的对象 。
浏览器的全部内容可以看成DOM,整个浏览器可以看成BOM。
BOM对象:
- window: Bom的核心对象是window,它表示浏览器的一个实例 。 在浏览器中,window对象有双重角色,即是浏览器窗口的一个接口,又是全局对象
- location:获取url地址信息
- navigator: 对象主要用来获取浏览器的属性,区分浏览器类型
- screen: 保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器的信息,比如像素宽度和像素高度
- history: 主要用来操作浏览器URL的历史记录,可以通过参数向前,向后,或者向指定URL跳转
26. BOM和DOM区别?
BOM(浏览器对象):
与浏览器交互的方法和对象
- BOM是浏览器对象模型,它指的是将浏览器当作一个对象来对待,这个对象主要定义了与浏览器进行交互的方法和接口
- BOM的核心是window,而window对象具有双重角色,它既是js访问浏览器窗口的一个接口,又是一个全局对象(Global)
- 这就意味着网页中定义的任何对象、变量和函数,都会作为全局对象的一个属性或者方法存在
DOM(文档对象模型):
处理网页内容的方法和接
DOM是文档对象模型,它指的是把文档当作一个对象来对待,这个对象主要定义了处理网页的内容和接口
27. javascript的内存(垃圾)回收机制
- 垃圾回收器会每隔一段时间找出那些不再使用的内存,然后为其释放内存
标记清除方法(mark and sweep)
- 这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”
- 垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
引用计数方法(reference counting)
- 在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
- 在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的, 也就是说只要涉及BOM及DOM就会出现循环引用问题。
28. javascript 代码中的"use strict";是什么意思?说说严格模式的限制
- use strict是一种ECMAscript 5 添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行,使JS编码更加规范化的模式,消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为
- 限制:
- 变量必须声明后再使用
- 函数的参数不能有同名属性
- 不能使用with语句
- 禁止this指向window
二. ES6篇
1. 新增语法
1.可定义常量的 const 和 块级变量 let 关键字 2.模板字符串 ` ` 3.展开运算符 … 4.块级作用域 { } 5.箭头函数 ()=> {} 6.属性简写 { a: a} ==> { a } 7.解构赋值 let [a, b, c] = [1, 2, 3]; let obj = { b: 10 } const { b } = obj 8.新增了promise 9.新增了模块化 10.新增Set、Map数据结构
2. 暂时性死区
在代码块内,使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
3. var、let、const 区别
Var:作用域为全局作用域和函数作用域,没有块级作用域,支持变量提升
Let:作用域为块级作用域,不支持变量提升,不允许重复声明,存在暂时性死区
Const:作用域为块级作用域,不支持变量提升,不允许重复声明,存在暂时性死区,(一旦声明一个变量就不能改变,否则报错)
4. 对箭头函数的理解
箭头函数是ES6中的提出来的,它不同于传统JavaScript中的函数,它不可以使用arguments参数,没有prototype显式原型属性,也没有属于自己的
this
指向,它所谓的this
是捕获其所在上下文的this
值作为自己的this
值,并且由于没有属于自己的this
,所以是不会被new调用的
5. 对this对象的理解
this
是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断。
6. this指向一般有几种情况
1) 函数调用模式,全局作用域或者普通函数(包括普通函数、定时器函数、立即执行函数)自执行中this指向全局对象window
函数的形式调用时,this指向window
//全局作用域
console.log(this);//Window
//普通函数
function fn(){
console.log(this); //Window
}
fn(); //函数加括号调用叫函数自执行,函数自执行时,内部的this指向顶层对象/window
2) 方法调用模式,以方法的形式调用时,this 指向调用方法的那个对象
//以对象字面量的方式创建一个对象
var obj={
name:'terry',
sayName: function(){
console.log(this);
} //将函数作为一个对象属性的值来调用(即以方法的形式调用)
}
obj.sayName(); // ===>此时会输出obj这个对象
3) 构造器调用模式,以构造函数的形式调用时,this 指向实例对象
// 构造一个存放人信息的函数
function Person(name,age,gender){
this.name=name,
this.age=age,
this.gender=gender,
this.sayName=function(){
alert(this.name)
}
};
var per = new Person('terry',18,'女') //定义一个新的对象,将函数赋值给它
per.sayName() //此时会输出'terry',
4) apply 、 call 和 bind 调用模式,使用 call 、bind和 apply 调用时,this 指向指定的那个对象
//创建一个函数
function fun(a,b){
console.log('a='+a);
console.log('b='+b)
console.log(this.name) //这里大家也可以打印console.log(this),最后输出的是obj这个对象;
//this.name输出的是obj这个对象的name属性的值。
}
//创建一个对象
var obj={
name:'obj',
};
//用对象来调用函数fun,并传入实参call()
fun.call(obj,1,2); //输出的结果a=1 b=2 obj
//apply() 方法
fun.apply(obj,[1,2]); //输出的结果也是a=1 b=2 obj
7. call、apply、bind 的用法以及区别
相同点:
三个方法都是挂载在Function对象原型上的方法,因此调用他们的对象必须是一个函数,这三个方法都是用来改变绑定函数执行时 this 的指向,同时支持传参
不同点:
call、apply的区别:接受参数的方式不一样,apply的第二个参数接受的数组
bind 返回值是一个函数,不会立即执行,需要调用,传参和 call 一样。而 apply、call 立即执行
1) call:
call函数主要接受2种参数,第一是this指向的转移对象,如果不传参数,或者第一个参数是null或nudefined,this都指向window(非严格模式)。接下来的参数则是函数可能会遇到的可选参数。
const A = { name : 'apple', showName: function(param){ console.log(this.name,param) // pear ppp } } let pear = {name: 'pear'}; A.showName.call(pear,'ppp')
2) apply:
apply的用法其实和call基本没啥区别,主要的区别在于对参数的传递,call和apply的第一个参数都是要改变上下文的对象,而call从第二个参数开始以参数列表的形式展现,apply则是把除了改变上下文对象的参数放在一个数组里面作为它的第二个参数。
const A = { name : 'apple', showName: function(param1,param2,param3){ console.log(this.name,param1,param2,param3) } } let pear = {name: 'pear'}; A.showName.apply(pear,['ppp','kkk','sss']); // pear ppp kkk sss
3) bind:
bind相比于call而言,并不是马上调用函数和直接改变函数的上下文,而是返回改变上下文之后的函数。就是不会立即执行,需要调用函数。
const A = { name : 'apple', showName: function(param1){ console.log(this.name,param1) } } let pear = {name: 'pear'} const method = A.showName.bind(pear,'aa'); //method(); // undefined
8. new操作符的实现步骤
- 创建一个新的空对象
- 将构造函数的作用域赋给新对象(也就是将新对象的 proto 属性指向构造函数的 prototype 属性)
- 构造函数中的this指向新对象,执行构造函数中的代码(也就是为这个对象添加属性和方法)
- 返回新的对象。判断函数的返回值类型,如果是值类型,返回创建的空对象。如果是引用类型,就返回这个引用类型的对象。
//具体实现
function objectFactory() {
let newObject = null;
let constructor = Array.prototype.shift.call(arguments);
let result = null;
// 判断参数是否是一个函数
if (typeof constructor !== "function") {
console.error("type error");
return;
}
// 新建一个空对象,对象的原型为构造函数的 prototype 对象
newObject = Object.create(constructor.prototype);
// 将 this 指向新建对象,并执行函数
result = constructor.apply(newObject, arguments);
// 判断返回对象
let flag = result && (typeof result === "object" || typeof result === "function");
// 判断返回结果
return flag ? result : newObject;
}
// 使用方法
objectFactory(构造函数, 初始化参数);
9. async/await的理解
async/await其实是
Generator
的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的,async用来声明一个函数是异步的,而await是等待这个异步方法执行完毕
1)async
async 函数返回的是一个 Promise 对象,如果在函数中直接return一个直接量,async会把这个直接量通过promise.resolve封装成一个promise对象
async function test() { return "test" } let res = test() console.log(res);
2)await
如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果
function time() { return new Promise((resolve)=> { setTimeout(()=> { resolve('await') },3000) }) } async function test() { let res = await time() console.log(res) // 输出 await }
10. 对 promise 的理解
1) 原理:
promise是ES6提供的一个构造函数,主要解决了异步多层嵌套回调(地狱回调)的问题,让代码的可读性更高,更容易维护。
可以使用promise构造函数new一个实例,promise构造函数接受一个函数作为参数,这个函数又可以接收两个参数,分别是‘resolve’和‘reject’,‘resolve’将promise由等待pending变为
Fulfilled
成功态,‘reject’则将状态由等待pending变成Rejected
失败态。一旦状态改变,就不会再变。
实例创建完成后,如果实例状态发生改变的话,会触发then()的回调函数,then()也是可以接收两个参数,第一个参数是Fulfilled
状态的回调函数,第二个参数是Rejected
状态的回调函数。如果写了catch()方法,那么then()方法可以不接收第二个参数,因为
Rejected
失败态的话会被catch捕捉到,调用catch的方法,而是Fulfilled
成功态的话,只执行then()的第一个参数的回调函数。then和catch最终返回的也是一个promise,所以可以链式调用。then方法和catch方法只要不报错,返回的都是一个fulfilled状态的promise对象。
2)特点:
- 对象的状态不受外界影响,promise对象代表一个异步操作,有三种状态:
Pending:/*初始态*/ Fulfilled:/*成功态*/ Rejected:/*失败态*/
- promise对象的状态改变,只能有两个可能, 从pending变为resolved 或者 从pending变成rejected,一旦状态改变,就不会在变。
- resolve方法的参数是then中回调函数的参数,reject方法种的参数是catch中的参数
- then方法和catch方法只要不报错,返回的都是一个fulfilled状态的promise
3)方法:
then()
是实例状态发生改变时的回调函数,第一个参数是
resolved
状态的回调函数,第二个参数是rejected
状态的回调函数catch()
用于指定发生错误时的回调函数
finally()
用于指定不管 Promise 对象最后状态如何,都会执行的操作
all()
接收一个数组参数,数组中的元素都是Promise对象。该方法返回一个新的Promise对象,若数组中所有的Promise状态都是成功,则返回的Promise也成功;若数组中至少有一个Promise对象失败,则返回的Promise也失败。
race()
效果和all类似,使用方法也一样。区别在于,race方法返回的Promise的状态和数组中最先完成状态改变的Promise的状态一致。