1.js数据类型有哪些?
基本数据类型:Number、String、Null、Undefined、Boolean、Symbol(ES6新增的,值得唯一性)
引用数据类型:Object、Array、Function
2.如何判断数据类型?
typeof
instanceof 返回true/false
constructor(对象的constructor属性 constructor可以当做prototype的属性, a.constructor = a.proto.constructor = A.prototype.constructor =A)
Object.prototype.toString
Array.isArray()
使用方法:
let arr = [1, 2]
console.log(typeof 3);//number
console.log(typeof null);// object
console.log(typeof arr);// object
console.log(arr instanceof Array); //true
console.log(arr.constructor === Array);//true
console.log(Object.prototype.toString.call(arr));//true
console.log(Array.isArray(arr));
3.new关键字做了什么?
1、创建一个空对象
2、用this指向这个对象
3、执行构造函数的代码,给这个对象增加属性和方法
4、返回这个对象
4.this的指向
1.全局的环境下this是指向window 的
2.普通函数this指向:函数的this指向遵循一个基本原则:谁调用的函数,函数的this就指向谁,否则指向全局
3.对象中的this指向:对象内部方法的this指向调用这些方法的对象,也就是谁调用就指向谁。
4.箭头函数中的this指向:箭头函数本身是没有 this,箭头函数 this 是定义箭头函数时父级作用域的 this。
5.构造函数中的this指向:指向new出来的实例对象。
6.原项链中的this指向:this这个值在一个继承机制中,仍然是指向它原本属于的对象,而不是从原型链上找到它时,它所属于的对象。
5.Javascript事件流
DOM标准规定事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。 事件 捕获阶段:实际目标在捕获阶段不会接收事件。也就是在捕获阶段,事件从document到再到就停 止了。 处于目标阶段:事件在上发生并处理。但是事件处理会被看成是冒泡阶段的一部分。 冒泡 阶段:事件又传播回文档。
冒泡事件:当你点击目标元素的时候,当前所触发的一些事件会向父元素中传递,这就是所谓的事件冒泡。
捕获事件:当你点击目标元素的时候,在该目标元素上点击触发的事件,会从父元素向下传递。
6.怎么阻止冒泡事件?
w3c: event.stopPropagation()
IE: window.event.cancelBubble = true
7.怎么阻止默认事件?
event.preventDefault()
8.什么是事件委托?
用事件冒泡的原理,自己所触发的事件, 让他的父元素代替执行
9.防抖和节流
防抖就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
应用场景:登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖。
//用定时器来实现防抖
function debounce(fn,wait) {
var timer=null;
return function() {
//保存当前调用的dom对象
var _this=this;
clearTimeout(timer)
timer=setTimeout(function() {
fn.apply(_this,arguments)
},wait)
}
}
节流就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率(事件按照固定的时间频率执行)
应用场景:scroll 事件,每隔一秒计算一次位置信息。
//时间戳版本实现节流
function throttle(func,wait) {
//定义初始时间
var oldTime=0;
return function() {
var _this=this;
var args=arguments;
//当前时间戳
var newTime=+new Date();
//判断用当前时间减去旧的时间,如果大于wait指定的时间就会触发
if(newTime-oldTime>wait) {
//执行触发的函数
func.apply(_this,args)
//将旧时间更新
oldTime=newTime;
}
}
}
10.什么是深浅拷贝怎么实现?
浅拷贝:只是拷贝一层, 更深层次对象级别的只拷贝引用. 不拷贝地址,两者属性值指向同一内存空间。简单来讲,就是改变其中一个对象,另一个对象也会跟着改变。
深拷贝:拷贝对象各个层级的属性。简单的讲,就是复制出来的每个对象都有属于自己的内存空间,不会互相干扰。
深拷贝实现方法:
// 深拷贝:对对象内部进行深拷贝,支持 Array、Date、RegExp、DOM
const deepCopy = (sourceObj) => {
// 如果不是对象则退出(可停止递归)
if(typeof sourceObj !== 'object') return;
// 深拷贝初始值:对象/数组
let newObj = (sourceObj instanceof Array) ? [] : {};
// 使用 for-in 循环对象属性(包括原型链上的属性)
for (let key in sourceObj) {
// 只访问对象自身属性
if (sourceObj.hasOwnProperty(key)) {
// 当前属性还未存在于新对象中时
if(!(key in newObj)){
if (sourceObj[key] instanceof Date) {
// 判断日期类型
newObj[key] = new Date(sourceObj[key].getTime());
} else if (sourceObj[key] instanceof RegExp) {
// 判断正则类型
newObj[key] = new RegExp(sourceObj[key]);
} else if ((typeof sourceObj[key] === 'object') && sourceObj[key].nodeType === 1 ) {
// 判断 DOM 元素节点
let domEle = document.getElementsByTagName(sourceObj[key].nodeName)[0];
newObj[key] = domEle.cloneNode(true);
} else {
// 当元素属于对象(排除 Date、RegExp、DOM)类型时递归拷贝
newObj[key] = (typeof sourceObj[key] === 'object') ? deepCopy(sourceObj[key]) : sourceObj[key];
}
}
}
}
return newObj;
}
// deepCopy 函数测试效果
const objA = {
name: 'jack',
birthday: new Date(),
pattern: /jack/g,
body: document.body,
others: [123,'coding', new Date(), /abc/gim,]
};
const objB = deepCopy(objA);
console.log(objA === objB); // false
console.log(objA.others === objB.others); // false
console.log(objA, objB); // 对象内容一样
11.call、bind和apply的区别
1、都可以更改this指向。
2、call,bind后面的第一个参数是指向的对象,第二个参数是往对象传的值。
3、apply后面的第一个参数是指向的对象,第二个参数是数组,数组里面是往对象传的值(arguments 全部参数)
4、call和apply更改this指向会自动调用, bind需要手动调用返回的是一个函数。
12.cookie,sessionStorage和localStorage的区别是什么?
1、都可以用来存储数据
2、cookie一条数据大小不能超过4KB ,最多不能存储超过20条,如果没有设置过期时间,那么在浏览器关闭后消失
3、sessionStorage是会话存储,一条大小不能超过5M,数量没有限制,关掉页面数据消失
4、localStorage本地存储,一条大小不超过5M,数量没有限制,除非主动删除,否则数据不会消失
13.什么是原型链 prototype原型 作用域?
当访问一个对象的属性时,会在这个对象本身属性上查找。如果没有,则查找它的 proto 隐式原型,构造函数的显示原型。如果还没有,就会在 构造函数的显示原型 的 proto 上查找。这样一层一层向上找,形成一个链式解构,我们称为原型链。
作用域:当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain,不简称sc)来保证对执行环境有权访问的变量和函数的有序访问。作用域第一个对象始终是当前执行代码所在环境的变量对象(VO)。
14.什么是闭包?闭包的作用场景是什么?
闭包:
闭包指的是一个函数可以访问另一个函数作用域中变量。
使用场景:
1.给对象设置私有变量并且利用特权方法去访问私有属性;
2.采用函数引用方式的setTimeout调用;
3.防抖节流;
4.使用闭包创建多个计数器;
5.小范围代替全局变量;
6.模块化
闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏。
15.同步异步的区别是什么?
同步:上一个任务没有执行完,下一个任务不能开启。
异步:即便上一个任务没有执行完,下一个任务仍然可以开启。
16.Javascript中异步操作
setTimeout 延时器
setInterval 计时器
数据请求
Promise
事件监听
回调函数
17.ES6有哪些新特性?
1.let关键字,声明变量;
2.const关键字,声明常量;
3.模板字符串;
4.箭头函数;
5.对象和数组解构;
6.Map、Set集合;
7.Promise对象;
8.引入class关键字;
9.ES6模块化。
18. var 和let、Const的区别是什么?
(1) let声明的变量只能先声明后赋值;
(2) 只能使用let对同一个变量声明一次;
(3) let声明的变量有块级作用域 ;
var 和let定义变量 const常量
- let、const声明的变量仅在块级作用域内有效,var 声明变量是全局的,没有块级作用域功能
- let 、const 不存在变量提升,在声明前使用会报错:Uncaught ReferenceError, var 存在变量在var声明之前就访问对应的变量,则会得到undefined。
- let 、const不能在同一块级作用域内重复申请var 可以重复声明。
19.数组的常用方法有哪些?
1、sort( ):sort 排序 如果下面参数的正反 控制 升序和降序 ,返回的是从新排序的原数组
2、splice( ):向数组的指定index处插入 返回的是被删除掉的元素的集合,会改变原有数组;截取类 没有参数,返回空数组,原数组不变;一个参数,从该参数表示的索引位开始截取,直至数组结束,返回截取的 数组,原数组改变;两个参数,第一个参数表示开始截取的索引位,第二个参数表示截取的长度,返回截取的 数组,原数组改变;三个或者更多参数,第三个及以后的参数表示要从截取位插入的值。会改变原数据。
3、pop( ):从尾部删除一个元素 返回被删除掉的元素,改变原有数组。
4、push( ):向数组的末尾追加 返回值是添加数据后数组的’新长度,改变原有数组。
5、unshift( ):向数组的开头添加 返回值是添加数据后数组的新长度,改变原有数组。
6、shift( ):从头部删除一个元素 返回被删除掉的元素,改变原有数组。
7、reverse( ): 原数组倒序 它的返回值是倒序之后的原数组
8、concat( ):数组合并。
9、slice( ):数组元素的截取,返回一个新数组,新数组是截取的元素,可以为负值。
10、join( ):讲数组进行分割成为字符串 这能分割一层在套一层就分隔不了了
11、toString( ):数组转字符串;
12、toLocaleString( ):将数组转换为本地数组。
13、forEach( ):数组进行遍历;
14、map( ):没有return时,对数组的遍历。有return时,返回一个新数组,该新数组的元素是经过过滤(逻辑处理)过的函数。
15、filter( ):对数组中的每一运行给定的函数,会返回满足该函数的项组成的数组。
16、every( ):当数组中每一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
17、some( ):当数组中有一个元素在callback上被返回true时就返回true。(注:every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。
19.isArray() 判断是否是数组20. indexOf 找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1
20.lastIndexOf 它是从最后一个值向前查找的 找索如果找到了就会返回当前的一个下标,若果没找到就会反回-1
21.Array.of() 填充单个值
22.Array.from() 来源是类数组(转为数组)
20.字符串常用的方法有哪些?
1、replace() 把指定的字符串替换成为别的字符
2、concat( ):返回新的字符串**,将一个或多个字符串与原字符串连接合并
3、indexOf( ):检索字符串,返回第一次出现的索引,没有出现则为-1
4、lastIndexOf(searchValue[ fromIndex]) 返回从字符串尾部开始第一次出现的索引,没有则-1,fromIndex的值相对于从尾部开始的索引
5、split( ):返回一个以指定分隔符出现位置分隔而成的一个数组,数组元素不包含分隔符
6、substr( ):从起始索引号提取字符串中指定数目的字符;
7、substring( ):提取字符串中两个指定的索引号之间的字符;
8、slice( ):提取字符串片段,并在新的字符串中返回被提取的部分;
9、search(regexp)返回首次匹配到的索引,没有则-1,执行正则表达式和 String 对象之间的一个搜索匹配17、toString()返回一个表示调用对象的字符串,该方法返回指定对象的字符串形式
10、valueOf( ):返回某个字符串对象的原始值;
11、trim( ):删除字符串两边的空格;
12、trimeState 取出开始的空格
13、trimeEnd 去除末尾空格14、includes(searchString[, position])返回boolean,判断一个字符串是否包含在另一个字符串中,从postition索引开始搜寻,默认0
21.数组怎么进行去重 (3种以上方法)
利用ES6 Set去重(ES6中最常用)
利用for嵌套for,然后splice去重(ES5中最常用)
利用indexOf去重 indexOf(‘元素’) 存在返回元素第一次出现的索引位置,从索引0开始 该元素不存在返回-1
利用includes 可以判断一个数组中是否包含某一个元素,并返回true 或者false
利用filter
22.数组扁平化
定义:将一个多维数组转换成一个一维数组。
方法:递归、利用数组的reduce方法(reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值)、利用es6的flat方法(flat(n)将每一项的数组偏平化,n默认是1,表示扁平化深度,Infinity无限次)、利用扩展运算符…
案例:
// 判断是否里边还有没拍扁的数组
function haveArr(arr) {
return arr.some(item => {
return item instanceof Array
})
}
function flatern(arr) {
let resArr = [].concat(...arr)
if (haveArr(resArr)) {
return flatern(resArr)
}
return resArr
}
let arr = [1, 1, 2, [3, [4, 5]]]
let arrx = flatern(arr)
23.递归
递归,就是在运行过程中自己调用自己。
构成递归需具备的条件:
1. 自身调用:原问题可以分解为子问题,子问题和原问题的求解方法是一致的,即都是调用自身的同一个函数。
2. 终止条件:递归必须有一个终止的条件,即不能无限循环地调用本身。
24.什么是纯函数?
纯函数的概念:一个函数的返回结果只依赖其参数,并且执行过程中没有副作用。
25. 什么是高阶函数?
一个函数作为另外一个函数的参数或者一个函数的返回值为另外一个函数这种函数就称之为高阶函数。
26.函数柯里化及其用途
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
用途:参数复用、提前确认或返回、延迟计算或运行
27.怎么解决异步回调地狱问题?
Promise、generator、async await
28.说一下Promise使用? 与async await区别
Promise是异步编程的一种解决方案,在ES6中Promise被列为了正式规范,统一了用法,原生提供了Promise对象。Promise承诺:默认情况下是等待状态pending,如果有一天状态转变为成功就成功了(Resolved),如果状态变成失败就失败了( Rejected)。状态一旦改变了就不能再改变了。
Promise主要用于异步计算;
可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果;
可以在对象之间传递和操作Promise,帮助我们处理队列。
async-await:用于解决异步回调, 用同步的写法使得可读性更强,同时方便try catch捕获异常。
区别:
1.Async Await是基于promise实现,是改良版的promise,使代码看起来更加简洁,异步代码执行像同步代码一样;
2.是语法糖;
3.减少回调。
29.怎么将伪数组转换成真正的数组?
1、Array.prototype.slice.call(arguments,0) // 使用slice方法实现
2、Array.from(arguments) // 使用ES6中的函数
3、[…arguments]
30.斐波那契数列怎么实现?
function fibonacci(n) {
if (n == 1 || n == 2) {
return 1
};
return fibonacci(n - 2) + fibonacci(n - 1);
}
fibonacci(30)
// 改进递归-利用闭包特性把运算结果存储在数组里,避免重复计算
var fibonacci = function () {
let memo = [0, 1];
let fib = function (n) {
if (memo[n] == undefined) {
memo[n] = fib(n - 2) + fib(n - 1)
}
return memo[n]
}
return fib;
}()
fibonacci(30);
//for循环+解构赋值
var fibonacci = function (n) {
let n1 = 1; n2 = 1;
for (let i = 2; i < n; i++) {
[n1, n2] = [n2, n1 + n2]
}return n2
}
fibonacci(30)
31.冒泡算法
//从小到大
function BubbleSort(arr){
var i,j,temp;
for(i=0;i<arr.length-1;i++){
for(j=i+1;j<arr.length;j++){
if(arr[i]>arr[j]){
temp=arr[j];
arr[j]=arr[i];
arr[i]=temp;
}
}
}
return arr;
}
var arr=[10,7,9,11,22,33,4,2,0,1000];
BubbleSort(arr); 17 console.log(arr); //[0, 2, 4, 7, 9, 10, 11, 22, 33, 1000]
32.快排算法
var quickSort_New = function(ary, left, right) {
if(left >= right) {
return ary;
}
var i = left,
j = right;
base = ary[left];
while (i < j) {
// 从右边起,寻找比基数小的数
while (i<j && ary[j] >= base) {
j--;
}
// 从左边起,寻找比基数大的数
while (i<j && ary[i] <= base) {
i++
}
if (i<j) {
var temp = ary[i];
ary[i] = ary[j];
ary[j] = temp;
}
}
ary[left] = ary[i];
ary[i] = base;
quickSort_New(ary, left, i-1);
quickSort_New(ary, i+1, right);
return ary;
}
33.面向对象继承方式
原型链的方式来实现继承
借用构造函数
组合继承
寄生式继承
寄生式组合继承
34. EventLoop事件循环
JS是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。微任务队列的代表就是,Promise.then,MutationObserver,宏任务的话就是setImmediate setTimeout setInterval。
35.介绍一下垃圾回收机制
JS的垃圾回收机制是为了以防内存泄漏,垃圾回收机制就是间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的内存。(标记清除法,引用清除法)
如何避免内存泄漏:
减少全局变量;
移除被遗忘的定时器或回调函数;
及时释放闭包中的变量;
及时清除不再使用的DOM引用。
36.Proxy的作用?
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(metaprogramming),即对编程语言进行编程。Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
37.前端优化之渲染百万条数据不卡顿
<!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>Document</title>
</head>
<body>
<ul></ul>
<script>
// createDocumentFragment
// requestAnimationFrame
// 百万条数据
let total = 1000000;
// 单次插入 可自定义
let once = 20;
// 需要插入的次数 向上取整
let loopCount = Math.ceil(total / once);
// 当前渲染次数
let countRender = 0;
function render() {
// 需要插入的目标对象
const targetElement = document.querySelector("ul");
// 文档因为存在片段于内存中,并不在DOM树中,将所以子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。
// 创建一个虚拟Dom节点 插入真实文档之前不会触发dom渲染、回流等操作
// 因此能够很大程度减少dom操作所带来的的性能损耗
const fragment = document.createDocumentFragment();
// 对虚拟节点插入dom节点,也不会触发真是dom操作,同上
for (let i = 0; i < 20; i++) {
// 搞个节点
const li = document.createElement("li");
// 给li搞点内容
li.innerHTML = Math.random();
// 插入到虚拟节点
fragment.appendChild(li);
}
// 插入到真实节点的时候,只会把虚拟fragment下的子孙节点插入
targetElement.appendChild(fragment);
// 渲染次数加1,控制递归的次数
countRender++;
// // 递归调用
if (countRender < loopCount) {
// window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
// 回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。
// 能够把每次dom的操作汇总在下一次重绘之前更新动画帧
// 因此在浏览器单线程的机制下,能够无卡顿的加载,百万级列表
window.requestAnimationFrame(render);
}
}
// 执行渲染
render();
</script>
</body>
</html>
38. 手写实现Promise
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
const resolvePromise = (promise2, x, resolve, reject) => {
// 自己等待自己完成是错误的实现,用一个类型错误,结束掉 promise Promise/A+ 2.3.1
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// Promise/A+ 2.3.3.3.3 只能调用一次
let called;
// 后续的条件要严格判断 保证代码能和别的库一起使用
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
try {
// 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候) Promise/A+ 2.3.3.1
let then = x.then;
if (typeof then === 'function') {
// 不要写成 x.then,直接 then.call 就可以了 因为 x.then 会再次取值,Object.defineProperty Promise/A+ 2.3.3.3
then.call(x, y => { // 根据 promise 的状态决定是成功还是失败
if (called) return;
called = true;
// 递归解析的过程(因为可能 promise 中还有 promise) Promise/A+ 2.3.3.3.1
resolvePromise(promise2, y, resolve, reject);
}, r => {
// 只要失败就失败 Promise/A+ 2.3.3.3.2
if (called) return;
called = true;
reject(r);
});
} else {
// 如果 x.then 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.3.4
resolve(x);
}
} catch (e) {
// Promise/A+ 2.3.3.2
if (called) return;
called = true;
reject(e)
}
} else {
// 如果 x 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.4
resolve(x)
}
}
class Promise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks= [];
let resolve = (value) => {
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
}
let reject = (reason) => {
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
}
try {
executor(resolve,reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
//解决 onFufilled,onRejected 没有传值的问题
//Promise/A+ 2.2.1 / Promise/A+ 2.2.5 / Promise/A+ 2.2.7.3 / Promise/A+ 2.2.7.4
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
//因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后 then 的 resolve 中捕获
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
// 每次调用 then 都返回一个新的 promise Promise/A+ 2.2.7
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
//Promise/A+ 2.2.2
//Promise/A+ 2.2.4 --- setTimeout
setTimeout(() => {
try {
//Promise/A+ 2.2.7.1
let x = onFulfilled(this.value);
// x可能是一个proimise
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
//Promise/A+ 2.2.7.2
reject(e)
}
}, 0);
}
if (this.status === REJECTED) {
//Promise/A+ 2.2.3
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
}, 0);
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e)
}
}, 0);
});
this.onRejectedCallbacks.push(()=> {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0);
});
}
});
return promise2;
}
}
39. 手写bind函数
//bind实现要复杂一点 因为他考虑的情况比较多 还要涉及到参数合并(类似函数柯里化)
Function.prototype.myBind = function (context, ...args) {
if (!context || context === null) {
context = window;
}
// 创造唯一的key值 作为我们构造的context内部方法名
let fn = Symbol();
context[fn] = this;
let _this = this;
// bind情况要复杂一点
const result = function (...innerArgs) {
// 第一种情况 :若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符使用,则不绑定传入的 this,而是将 this 指向实例化出来的对象
// 此时由于new操作符作用 this指向result实例对象 而result又继承自传入的_this 根据原型链知识可得出以下结论
// this.__proto__ === result.prototype //this instanceof result =>true
// this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
if (this instanceof _this === true) {
// 此时this指向指向result的实例 这时候不需要改变this指向
this[fn] = _this;
this[fn](...[...args, ...innerArgs]); //这里使用es6的方法让bind支持参数合并
delete this[fn];
} else {
// 如果只是作为普通函数调用 那就很简单了 直接改变this指向为传入的context
context[fn](...[...args, ...innerArgs]);
delete context[fn];
}
};
// 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法
// 实现继承的方式: 使用Object.create
result.prototype = Object.create(this.prototype);
return result;
};
//用法如下
// function Person(name, age) {
// console.log(name); //'我是参数传进来的name'
// console.log(age); //'我是参数传进来的age'
// console.log(this); //构造函数this指向实例对象
// }
// // 构造函数原型的方法
// Person.prototype.say = function() {
// console.log(123);
// }
// let obj = {
// objName: '我是obj传进来的name',
// objAge: '我是obj传进来的age'
// }
// // 普通函数
// function normalFun(name, age) {
// console.log(name); //'我是参数传进来的name'
// console.log(age); //'我是参数传进来的age'
// console.log(this); //普通函数this指向绑定bind的第一个参数 也就是例子中的obj
// console.log(this.objName); //'我是obj传进来的name'
// console.log(this.objAge); //'我是obj传进来的age'
// }
// 先测试作为构造函数调用
// let bindFun = Person.myBind(obj, '我是参数传进来的name')
// let a = new bindFun('我是参数传进来的age')
// a.say() //123
// 再测试作为普通函数调用
// let bindFun = normalFun.myBind(obj, '我是参数传进来的name')
// bindFun('我是参数传进来的age')
RAF和RIC是什么?
requestAnimationFrame: 告诉浏览器在下次重绘之前执行传入的回调函数(通常是操纵 dom,更新动画的函数);由于是每帧执行一次,那结果就是每秒的执行次数与浏览器屏幕刷新次数一样,通常是每秒 60 次。
requestIdleCallback:: 会在浏览器空闲时间执行回调,也就是允许开发人员在主事件循环中执行低优先级任务,而不影响一些延迟关键事件。如果有多个回调,会按照先进先出原则执行,但是当传入了timeout,为了避免超时,有可能会打乱这个顺序。