无论我们是主动还是被动,面试总是我们不得不学会的一项技能;其中的基础又是重中之重,决定了我们是否能继续面下去。
本文主讲一些常用JS考点:
0)同步任务、异步任务、宏任务和微任务之间的关系
同步任务就是js单线程执行语言
异步任务包含宏任务和微任务
1)宏任务 微任务 (https://www.cnblogs.com/ckAng/p/11133643.html)
a. 宏任务(由宿主(JS允许的环境 一般为浏览器或者node)发起的)
微任务(由JS自身发起)
b. 宏任务一般是整体代码script setTimeout setInterval I/O UIrender ajax
微任务主要是Promise.then Object.observe MutationObserver catch finally
执行顺序优先级 同步 > 微任务 > 宏任务
2)eventLoop(事件循环)
a. 以JavaScript语言为例 它是一种单线程语言 所有任务都在一个线程上完成 一旦遇到大量任务或者遇到一个耗时的任务 网页就会出现假死 因为js停不下来 也就无法影响用户的行为
b. Event Loop应运而生 是指浏览器的一种解决JavaScript单线程运行时不会阻塞的一种机制 也就是我们经常使用异步的原理
c. 同步和异步任务分别进入不同的场所执行 所有同步任务都在主线程上执行 形成一个执行栈 而异步任务进入Event Table并注册回调函数 当这个异步任务有了执行结果 Event Table会将这个回调函数移入Event Queue 进入等待状态 当主线程内同步任务执行完成 会去Event Queue读取对应的函数 并结束它的等待状态 进入主线程执行(主线程不断重复上面3个步骤 也就是常说的Event Loop事件循环)
d. 执行栈在执行完同步任务后 查看执行栈是否为空 如果执行栈为空 就会去执行Task(宏任务) 每次宏任务执行完后 会检查微任务队列是否为空 如果不为空的话 会按照先入先出的规则全部执行完微任务后 设置微任务队列为null 然后再执行宏任务 如此循环
e. 整体script属于宏任务的概念
3)手写instanceof实现(用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上)
a. 实现思路 首先instanceof左侧必须是对象 才能找到它的原型链 instanceof右侧必须是函数 函数才有prototype属性 迭代处理 左侧对象的原型不等于右侧的prototype时 沿着原型链重新赋值左侧
b. 代码如下:
function instance_of(L, R) { const baseType = ['string', 'number', 'boolean', 'undefined', 'symbol']; if(baseType.includes(typeof(L))) return false; // 验证如果为基本数据类型 就直接返回false let RP = R.prototype; // 取R的显式原型 L = L.__protp__; // 取L的隐式原型 while(true) { // 循环判断prototype是否出现在实例对象的原型链上 if(L === null) return false; if(L === RP) return true; L = L.__protp__; // 向上一层原型链查找 } }
4)new的过程
a. 创建一个空对象 将它的引用赋给this 继承函数的原型
b. 通过this 将属性和方法添加至这个对象
c. 最后返回this指向的新对象 也就是实例 (如果没有手动返回其他的对象)
5)this指向
a. 第一准则是 this永远指向函数运行时所在的对象 而不是函数被创建是所在的对象
b. 普通的函数调用 函数被谁调用 this就是谁 构造函数的话 如果不用new操作符而直接调用 那即this指向window 用new操作符生成对象实例后 this就指向了新生成的对象
c. 匿名函数或 不处于任何对象中的函数指向window
d. 如果是call apply等(改变函数作用域) 指定的this是谁就是谁 每个函数都包含两个非继承而来的方法call apply 在js中 两者作用是一样的 都是为了改变某个函数运行时的上下文(context)而存在的 换句话说 就是为了改变函数体内部this的指向
6)call apply bind (获取另一个对象的指向 就能使用它的方法与变量)
代码如下:
// 写法 obj.myFun.call(db,'成都','上海'); obj.myFun.apply(db,['成都','上海']); obj.myFun.bind(db,'成都','上海')(); obj.myFun.bind(db,['成都','上海'])(); // 手写call es5 Function.prototype.call = function(content) { content = content || window; // 给执行上下文 添加默认值 content.fn = this; // 给content添加一个方法 指向this var args = []; // 保存参数集合 for(let i = 1; i < arguments.length; i++) { args.push(arguments[i]); } var res = args.length > 1 ? eval('content.fn(' + args + ')') : content.fn(); // 执行fn delete content.fn; // 删除fn方法 return res; } // 手写apply es6 Function.prototype._Apply = function(content, args) { content = content || window; args = []; if(!(args instanceof Object)) { // 如果第二个参数不是对象的实例 就返回一个错误 throw new TypeError("Create List From Array Like called on non-object"); } content.fn = this; // 显示绑定函数的this const res = arguments[1] ? content.fn(...arguments[1]) : content.fn() delete content.fn; // 删除fn方法 return res; } // 手写 bind es6 Function.prototype._bind = function(content = window) { const slice = Array.prototype.slice; const thatFunc = this; // 保存调用函数的this 也就是原函数 const args = [].slice.call(arguments, 1); // 获取第一个参数之后剩余的参数数组 return function() { const funcArgs = args.concat(slice.call(arguments)); // 合并返回的新函数的参数 return thatFunc.call(content, ...funcArgs); // 返回新函数调用的结果 } }
其实就是通过一个中间函数fn 将接受的参数arguments封装返回 就实现了当前作用域使用其他函数的内容
7)for in 和for of
a. for in {
(即可遍历对象 也可遍历数组)
遍历数组的问题(1.index索引为字符串型数字 不能直接进行几何运算 2.遍历顺序有可能不是按照实际数组的内部顺序 3.使用for in会遍历数组所有的枚举属性 包括原型 例如上溯的原型方法和method和name属性 所以for in更适合遍历对象 不要使用for in遍历数组)
}
b. for of 遍历数组 不可遍历对象(取索引用[index, val])
8)变量比较时 对象的隐式转换(valueOf 或者 toString转换为原始类型的值比较)
a. 如何声明a 令 a==1&&a==2&&a==3 返回true
b. a为对象 对象和原始数据类型比较会找对象的 valueOf方法(返回对象的原始值)如果没有则找toString()方法
9)判断汉字和字母
a. ASCII码比较 './images/ASCII码比较.jpg'
b. 面试题(js判断输入内容长度(不限中英文和数字))(https://www.jb51.net/article/48202.htm)
10)数据类型
a. 基础数据类型(string number boolean undefined null symbol bigint)
symbol(独一无二的值 作为属性名命名 可以保证不重名)
https://www.runoob.com/w3cnote/es6-symbol.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt
b. 引用数据类型(function object array)
11)箭头函数
a. 箭头函数不能用作构造器 和new一起使用会抛出错误(箭头函数没有自己的this 而是继承父作用域中的this)
b. 箭头函数没有prototype属性(因为不能通过new关键字调用)
c. 不绑定this 在箭头函数出现之前 每个新定义的函数都有他自己的this值
d. 用剩余参数语法表示 而不是arguments
e. 防止变量提升
f. 由于箭头函数没有自己的this指针 通过call或apply方法调用一个函数时 只能传递参数(不能绑定this)
12)gengerator函数(生成器)
a. 在JavaScript中 一个函数一旦开始执行 就会运行到最后或遇到return时结束 运行期间不会有其他代码能够打断它 也不能从外部再传入值到函数体内
b. 而gengerator函数的出现使得打破函数的完整运行成为了可能 其语法行为与传统函数完全不同
c. https://www.cnblogs.com/rogerwu/p/10764046.html
function关键字与函数名之间有一个*号(紧挨function关键字)
函数体内使用yeild表达式 定义不同的内部状态(可以有多个yeild)
直接调用Gengerator函数并不会执行 也不会返回运行结果 而是返回一个遍历器对象(Iterator Object)
依次调用遍历器的next方法 遍历Gengerator函数内部的每一个状态
13)手写promise.all
代码如下:
// 手写promise.all function promiseAll(args) { return new Promise((resolve, reject) => { const promiseResults = []; let iteratorIndex = 0; let fullCount = 0; for(const item of args) { let resultIndex = iteratorIndex; iteratorIndex += 1; Promise.resolve(item).then(res => { promiseResults[resultIndex] = res; fullCount += 1; if(fullCount === iteratorIndex) { resolve(promiseResults); } }).catch(err => { reject(err); }); } if(iteratorIndex === 0) { resolve(promiseResults); } }); } if(!Promise.all) { Promise.all = promiseAll; }
14)shim和polyfill有什么区别
a. 一个shim是一个库 它将一个新的api引入到一个旧的环境中 而且仅靠旧环境中已有的手段实现
b. 一个polyfill就是一个用在浏览器API上的shim 我们通常的做法是先检查当前浏览器是否支持某个API 如果不支持的话 就加载对应的polyfill 然后新旧浏览器就都可以使用这个API了
c. 示例:
if(!Number.isNaN) { Number.isNaN = function(num) { return(num !== num); } }
15)设计模式
a. 观察者模式和发布订阅模式的区别(https://www.jianshu.com/p/594f018b68e7)
b. 单例模式(保证一个类仅有一个实例 并提供一个全局的访问点) {
(无非是用一个变量来标志当前是否已经为某个类创建对象 如果是 则在下一次获取该类的实例时 直接返回之前创建的对象)
(只需要生成一个唯一对象的时候 比如说页面登录框 只可能有一个登录框 那么你就可以用单例的思路去实现他 当然不用单例的思想实现也行 那带来的结果可就是你每次要显示登录框的时候都要重新生成一个登录框并显示(耗费性能) 或者是不小心显示了两个登录框)
(https://www.cnblogs.com/yonglin/p/8080836.html)
}
c. 策略模式 (应用 促销方案 表单验证) {
(策略模式就是将一系列算法封装起来 并使他们相互之间可以转换 被封装起来的算法具有独立性 外部不可改变其特性)
(改变多个if-else的问题)
(https://zhuanlan.zhihu.com/p/146500964)
}
d. 工厂模式 {
(工厂模式是我们最常用的实例化对象模式了 使用工厂方法代替new操作的一种模式)
(因为工厂模式就相当于创建实例对象的new 我们经常要根据类Class生成实例对象 如A a = new A() 工厂模式也是用来创建实例对象的 所以以后new时就要多个心眼 是否可以考虑使用工厂模式 虽然这样做 可能多做一些工作 但会给你系统带来更大的可扩展性和尽量少的修改量)
}
e. 纯函数 {
(如果函数的调用参数相同 则永远返回相同的结果 它不依赖于执行期间 函数外部任何状态或数据的变化 必须只依赖于其输入参数)
(该函数不会产生任何可观察的副作用 例如网路请求 输入和输出设备或数据突变(mutation))
(一个函数的返回结果只依赖于它的参数 并且在执行过程里面没有副作用 我们就把这个函数叫做纯函数)
(const a = 1; const foo = (x, b) => x + b; foo(1, 2) // =3)
}
16)继承(原型链继承 构造函数继承 组合继承 原型式继承 寄生式继承 寄生组合式继承)
(https://www.cnblogs.com/ranyonsue/p/11201730.html)
17)进程和线程 (火车与车厢)