Javascript 有多少种数据类型,如何判断?
primitive types(基本类型,栈内存):Number String Boolean Null Undefined Symbol
object types(对象类型,堆内存): Object Array Date RegExp Map Set 等等
结合 typeof 和 instanceof 可以判断
一个通用的方法是 Object.prototype.toString.call(val)来判断。
function getType(val) {
let typeStr = Object.prototype.toString.call(val);
let reg = /\[object\s(\w+)\]/;
let type = reg.exec(typeStr)[1];
return type;
}
为什么说 Symbol 是基本数据类型?
Symbol 是没有构造函数 constructor 的,不能通过 new Symbol() 获得实例。
基本类型为什么也可以调⽅法,⽐如.toFixed() ?
基本类型都有对应的包装类,可以调⽅法是因为进⾏了⾃动装箱。
如何理解原型链?
思路:⾸先要说什么是原型,为什么要设计原型(共享属性和⽅法),然后说属性和⽅法查找的顺序,⾃然⽽然就谈到了原型链。原型链可以引申到继承,
继承要结合构造函数和原型。
每个对象都有原型,对象的原型可以通过其构造函数的 prototype 属性访问。查找㇐个对象的属性或⽅法时,如果在对象本身上没有,就会去其原型上查找;
⽽原型本身也是㇐个对象,如果在原型上也找不到,查找原型的原型,这样就会继续串起一个原型链,原型链的终点是null。
如何理解闭包?
思路:闭包由词法环境和函数组成
内部函数 inner 如果引⽤了外部函数 outer 的变量 a,会形成闭包。如果这个内部函数作为外部函数的返回值,就会形成词法环境的引⽤闭环(循环引⽤),对
应变量 a 就会常驻在内存中,形成⼤家所说的“闭包内存泄漏”。虽然闭包有内存上的问题,但是却突破了函数作⽤域的限制,使函数内外搭起了沟通的桥 梁。闭包也是实现私有⽅法或属性,暴露部分公共⽅法的渠道。还可以引申出柯⾥化,bind 等概念,⾯试官说“可以啊,⼩伙⼦!”
变量提升问题
JS 分为词法分析阶段和代码执⾏阶段。在词法分析阶段,变量和函数会被提升(Hoist),注意 let 和 const 不会提升。
⽽赋值操作以及函数表达式是不会被提 升的。 出现变量声明和函数声明重名的情况时,函数声明优先级⾼,会覆盖掉同名的变量声明!为了降低出错的⻛险,尽量避免使⽤hoist!
注意混杂了函数参数时的坑,主要还是考虑按两个阶段分析,包括函数参数也是有声明和赋值的阶段:
var obj = {
val: 5,
};
function test(obj) {
obj.val = 10;
console.log(obj.val);
var obj = { val: 20 };
console.log(obj.val);
}
test(obj);
console.log(obj.val);
输出 10 20 10
function test(obj) {
console.log(typeof obj)
function obj() {}
}
test({})
输出:undefined
暂时性死区
这个其实在 ES6 规范就有提到了:The variables are created when their containing is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated.
ECMAScript 2015 Language Specification – ECMA-262 6th Edition ()
通过 let 或 const 声明的变量是在包围它的词法环境实例化时被创建,但是在变量的词法绑定执⾏前,该变量不能被访问。
typeof a; // 报 Uncaught ReferenceError: a is not defined
let a;
var 是没有这个问题的,var 声明变量有变量提升,并且变量的初始值是 undefined 。
警告:养成先声明再使⽤的好习惯,别花⾥胡哨的!
诡异的块级函数声明
a = 3
{
a = 2
function a() {}
a = 1
}
console.log(a)
// 2
严格模式要注意的地⽅
● Module code is always strict mode code.
● All parts of a ClassDeclaration or a ClassExpression are strict mode code.
● 禁⽌意外创建全局变量,直接抛出错误 ● this 问题 ● arguments 和参数列表的独⽴性,并且 arguments 不可修改
● 给 NaN 赋值会抛出错误
● 对象操作:给不可写属性赋值(writable: false);给只读属性赋值(只有 get,没有 set);给不可扩展对象新增属性
● 严格模式要求参数名唯㇐,属性名唯㇐
● 禁⽌⼋进制数字
● 禁⽌对 primitive type 的变量加属性或⽅法
● 禁⽌使⽤with
● eval 不再为 surrounding scope 添加新的变量
● 禁⽌delete 声明的变量,禁⽌delete 不可删除的属性或⽅法
LHS 和 RHS 是什么?会造成什么影响?
LHS 是 Left Hand Side 的意思,左值查询㇐个变量,如果变量不存在,且在⾮严格模式下,就会创建㇐个全局变量。RHS 查询㇐个变量,如果变量不存在, 就会报错 ReferenceError。
常⻅的 JS 语法错误类型有哪些?举⼏个例⼦。
ReferenceError:引⽤了不存在的变量 TypeError:类型错误,⽐如对数值类型调⽤了⽅法。 SyntaxError:语法错误,词法分析阶段报错。 RangeError:数值越界。 URIError,EvalError
在 try-catch 或 with 中定义变量有什么影响?
catch⼦句和 with 语句中有⾃⼰的词法作⽤域,但是如果通过 var 定义变量,会直接影响 try-catch 或 with 语句所在词法作⽤域。
JS 中如何确定 this 的值?
如果是在全局作⽤域下,this 的值是全局对象,浏览器环境下是 window。
函数中 this 的指向取决于函数的调⽤形式,在㇐些情况下也受到严格模式的影响。
● 作为普通函数调⽤:严格模式下,this 的值是 undefined,⾮严格模式下,this 指向全局对象。
● 作为⽅法调⽤:this 指向所属对象。
● 作为构造函数调⽤:this 指向实例化的对象。
● 通过 call, apply, bind 调⽤:如果指定了第㇐个参数 thisArg,this 的值就是 thisArg 的值(如果是原始值,会包装为对象);如果不传 thisArg,要判断 严格模式,严格模式下 this 是 undefined,⾮严格模式下 this 指向全局对象。
⼿写专题
实现继承
说出中⼼思想,⽽不是列举被博客炒了㇐遍⼜㇐遍的冷饭。 实现继承有两个⽅⾯要考虑,㇐个是原型属性和⽅法的继承,另㇐个是构造器的继承。
function inherit(ChildClass, SuperClass) {
ChildClass.prototype = Object.create(SuperClass.prototype);
ChildClass.prototype.constructor = ChildClass;
}
function SuperClass() {
this.propA = "a";
}
function ChildClass() {
SuperClass.call(this);
this.propB ="b";
}
inherit(ChildClass, SuperClass);
function A(name) {
= name;
}
A.prototype.getName = function () {
console.log();//es5写法
}
function B(){
A.apply(this,arguments);
}
const _proto_ = Object.create(A.prototype);
_proto_.constructor = B;
B.prototype = _proto_;
const b = new B('lisa');
b.getName();
⼿写 apply, call, bind
call 和 apply 是⽤来绑定 this,并提供参数,执⾏后会⽴即运⾏原函数。call 以列表形式提供参数, apply 以数组形式提供传参。
● ⼿写 call
Function.prototype.myCall = function (context) {
var type = typeof context;
//判断改变后的上下文对象是null和undefined的时候 this应该指向window
if (context === null || context === undefined) {
context = window;
}
//如果改变后的上下文对象是基本包装类型,则this指向其包装对象
switch (type) {
case "number":
context = new Number(context);
break;
case "boolean":
context = new Boolean(context);
break;
case "string":
context = new String(context);
break;
}
//获取第二个以后的参数,就是fn的参数
var arg = Array.from(arguments).slice(1);
/**
梳理:
1.fn1.myCall调用,所以这里的this指向的就是fn1,
给context扩展一个方法,这个方法就是fn1:context[key] = this;
2.context就是改变之后的上下文对象
3.然后调用context的扩展的这个方法,fn1就会被调用,并且this指向了context
**/
//给context扩展的方法名要是一个独一无二的值,防止覆盖原有方法
var key = Date.now().toString(36);
context[key] = this;
//然后调用context的扩展的这个方法,fn1就会被调用,并且this指向了context
var result = eval("context[key](" + arg.toString() + ")");
//此时改变之后的上下文对象context就会多一个方法,使用完成之后要删除掉这个方法
delete context[key];
return result;
}
var obj = {
name: "lucky",
fn: function () {
console.log("hello")
}
}
function fn1(a, b) {
console.log(this, a + b)
return "123";
}
console.log(fn1.call(obj, 1, 2));
fn1.myCall(obj, 1, 2);
fn1.myCall(null, 1, 2);
fn1.myCall(undefined, 1, 2);
fn1.myCall(1, 1, 2);
fn1.myCall("str", 1, 2);
fn1.myCall(true, 1, 2);
● ⼿写 apply
Function.prototype.myApply = function (ctx) {
ctx = ctx || window;
let fn = Symbol();
ctx[fn] = this;
let result;
if (arguments[1]) {
result = ctx[fn](...arguments[1]);
} else {
result = ctx[fn]();
}
delete ctx[fn];
return result;
};
● ⼿写 bind
Function.proptype.zhBind = function(thisArg,...argArray){
//1.获取真实需要调用的函数
var fn = this
//2. 绑定this
thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg):window
function proxyFn(..args){
//3.将函数放到thisArg中进行调用
thisArg.fn = fn
//特殊:对两个传入的参数进行合并
var finalArgs = [...argArray,...args]
var result = thisArg.fn(...finalArgs)
delete thisArg.fn
//4.返回结果
return result
}
//返回要调用的函数本身,这里是添加过this绑定值的函数
return proxyFn
}
⼿写实现 new 操作符的能⼒
写实现new,只能 函数 new 程
- 创建 新对象obj
- 新对象obj的原型指 可以通过
- 执 构造函数函数
- 返回这个对象
function myNew(Constructor, ...args) {
var obj = Object.create(Constructor.prototype)
Constructor.call(obj, ...args)
return obj;
}
⼿写实现 instanceof
instanceof判断的是右操作数的prototype属性是否出现在左操作数的原型链上。核心是要拿到左操作数的原型进行检查,要顺着原型链检查。取得原型是利用了Object.getPrototypeOf(obj)。
// [1,2,3] instanceof Array ---- true
// L instanceof R
// 变量R的原型 存在于 变量L的原型链上
function instance_of(L,R){
// 验证如果为基本数据类型,就直接返回false
const baseType = ['string', 'number','boolean','undefined','symbol']
if(baseType.includes(typeof(L))) { return false }
let RP = R.prototype; //取 R 的显示原型
L = L.__proto__; //取 L 的隐式原型
while(true){ // 无线循环的写法(也可以使 for(;;) )
if(L === null){ //找到最顶层
return false;
}
if(L === RP){ //严格相等
return true;
}
L = L.__proto__; //没找到继续向上一层原型链查找
}
}
⼿写实现 Promise
这个㇐般不会直接出现吧,因为如果按 Promise/A+规范来,代码量不少,如果做题时能提供Promise/A+规范原⽂做参考,应该是能写出来的。我可以跟⾯试官说我 github 已经写过㇐个实现了吗?
promises-aplus: 基于Promises/A+规范自定义封装一个PromiseA类,实现ECMAScript2015中Promise的基本功能。 ()
⼿写 Promise.prototype.catch
⼿写 Promise.prototype.finally
⼿写 Promise.all
⼿写防抖节流
防抖 原理: 防抖( debounce ):不管事件触发频率多⾼,㇐定在事件触发 n 秒后才执⾏,如果你在㇐个事件触发的 n 秒内⼜触发了这个事件,就以新的事件 的时间为准, n 秒后才执⾏,总之,触发完事件 n 秒内不再触发事件, n 秒后再执⾏。(频繁触发就执⾏最后㇐次) 应⽤场景:
- 窗⼝⼤⼩变化,调整样式
- 搜索框,输⼊后 1000 毫秒搜索
- 表单验证,输⼊1000 毫秒后验证
- 频繁点击按钮,使⽤防抖避免重复提交请求 防抖实现: ● debunce 实则是个包装函数,通过传⼊操作函数和时间间隔,来返回㇐个新函数 ● 新函数中主要是通过定时器来设置函数调⽤的频率 ● flag 只有第㇐次触发的时候会⽴即执⾏
//flag 执
function debounce(handler,ms,flag){
let timer = null;
return function(...args){
clearTimeout(timer);
if(flag&&!timer){
handler.apply(this,args);
}
timer = setTimeout(()=>{
handler.apply(this,args);
},ms)
}
}
//demo
window.addEventListener('resize',debounce(handler,1000));
function handler(){
console.log('ok');
}
节流
原理: 节流( throttle ):不管事件触发频率多⾼,只在单位时间内执⾏㇐次。(频繁触发,还是按照时间间隔执⾏) 应⽤场景:
- ⿏标不断点击触发,mousedown(单位时间内只触发㇐次)
- 监听滚动事件,⽐如是否滑到底部⾃动加载更多,⽤throttle 来判断 节流实现 ● 和防抖不同的是,防抖中是取消定时器,节流中是定时器到时间⾃动执⾏,仅仅是将 timer 变量设置为 null ● 时间戳版:第㇐次执⾏,最后㇐次不执⾏
- 定时器版:第㇐次执⾏,最后㇐次不执⾏
⼿写深拷⻉
考虑多种数据类型的处理
实现柯⾥化
柯⾥化有延迟计算的作⽤,参数的缓存是通过闭包实现的,所以实现上可以是:
实现发布订阅模式
发布订阅模式本质上是实现㇐个事件总线。只要实现其核⼼的 on, emit, off, remove, once 等⽅法即可。
实现观察者模式
很⻓㇐段时间,我都在纠结发布订阅和观察者模式的区别,⽽没有找到⽐较让我满意的答案。甚⾄在我⾃⼰⼿动实现了两种模式的代码后,我还时不时地产 ⽣模糊,因为它们都有涉及事件和回调的概念。终于,我认清了,发布订阅模式是㇐个多事件的事件总线;⽽观察者模式是针对单主题的,这是⼆者最⼤的 区别。
限流调度器
进㇐个任务,taskCount+1,等 Promise finally 后,才 taskCount-1;如果 taskCount 达到 taskCountLimit 上限,不能进任务。
HTML5
sessionStorage 和 localStorage 的细节 ⽆痕模式会有㇐些影响,以在 Chrome 测试得出的结论为准。
sessionStorage
● 基于会话级的存储,浏览器会话结束时,sessionStorage 会被清除。 ● 刷新⻚⾯不会重置 sessionStorage。 ● 由㇐个⽗窗⼝打开的新窗⼝会共享同㇐个 sessionStorage。 ● ⼿动通过 URL 打开各⾃的标签⻚,sessionStorage 不共享。 ● 受同源策略限制。 ● ⽆痕模式下,从⽗窗⼝打开同源新窗⼝也⽆法共享 sessionStorage。 localStorage ● 除⾮⽤户⼿动清除,localStorage 永远不会⾃动失效。 ● 受同源策略限制。 ● 浏览器⽆痕浏览模式下,localStorage 会在私密窗⼝下最后㇐个标签⻚关闭时清除。
CSS
如何理解浮动?和绝对定位的区别是什么? 什么是 BFC?触发 BFC 的条件是?常⻅应⽤是啥? ● Block Format Context,块级格式化上下⽂ ● ㇐块独⽴的渲染区域,内部元素的渲染不会影响边界以外的元素 触发 BFC 的条件是: ● float 不是 none ● position 是 absolute 或 fixed ● overflow 不是 visible ● display 是 flex、inline-block 等常⻅应⽤: ● 清除浮动 什么是层叠上下⽂?
DOM
移动端 300ms 延迟是指什么? 早期的⽹站都是为 PC 端⽽设计,⽽在移动端访问就会显得⼤⼩不合适。iPhone 发布之前,就针对这个问题提出了双击缩放的概念。但是这就带来了㇐个问 题,如何判断㇐个⽤户到底是想点击㇐个元素,还是想双击缩放呢?早期移动端浏览器就只能在 touchend 事件之后再等待 300ms,判断⽤户是不是会再次点 击,如果⽤户在 300ms 内不会再次点击屏幕就触发 click 事件。但是这样就会造成 300ms 的延迟。
点击穿透问题?
框架
Vue 数据流问题响应式原理双向绑定原理依赖收集的过程 Patch 过程 nextTick 原理 watch 和 computed 原理 computed 是怎么收集依赖的? watchEffect 是怎么收集依赖的? React HTTP cookie HTTP 协议本身是⽆状态的,⽽Cookie 是在 HTTP 中的㇐个请求头,⽤来保存㇐⼩块数据,Cookie 会传递到服务端,⽤来作为会话标识。服务端可以通过 Set-Cookie 来设置 cookie,⽽客户端⽹⻚ 也可以通过 document.cookie 来操作 cookie。
document 操作 cookie
document.cookie 是可以读取所有 cookie 的,最后的值是类似这样的㇐段字符串。