JS 面试题汇总

说一下JS 中的数据类型有哪些

JS 数据类型包括 基本 / 引用 / 特殊 数据类型:

1.基本数据类型:String、Number、Boolean
2.引用数据类型:Object、Array、Function
3.特殊数据类型:Null、Undefined
4.原始数据类型 Symbol (ES6)
5.独一无二的值,即 Symbol('1’) != Symbol('1’)

追问:判断 JS 数据类型有几种方法

常用的有 typeof、instanceof,
不常用的有 constructor、 prototype / toString

1.typeof 是个一元运算,放在任意类型的运算数之前,返回一个 字符串 说明运算数的类型。
可检测出的类型有:
'number'、'string'、'boolean'、'object'
'undefined','function'、'symbol'
其中对象"object" 包括:Object、Array、new RegExp()、new Date() 和 Null 特殊类型
缺点:判断普通类型没有问题,但不能准确判断 引用数据类型

2.instanceof 运算符用来检测一个对象在其原型链中是否存在一个构造函数的 prototype 属性
通俗讲 instanceof 检测的是原型,检测左边的对象是否是右边类的实例

 [] instanceof Array ==> true

注意:instanceof 能够判断出 [] 是 Array 的实例,也是 Object 的实例
因为 [].proto 指向 Array.prototype,而 Array.prototype.proto 又指向了 Object.prototype,最终 Object.prototype.proto 指向了 null 原型链结束。
类似的还有 new Date(),new Error() 和 new 自定义类()
归纳:所有对象都是 Object 的实例 或 Object是一切对象的父对象

3.根据对象的 constructor 判断
原理:每个构造函数都有一个 constructor 属,指回它本身

[].coconstructor === Array ==> true

判断 数字、字符串、函数 和 日期时,必须得用关键字 new 创建才行
因为只有构造函数才有 constructor 属性,还有两点需要注意:

null 和 undefined 是无效的对象,因此不会有 constructor 存在,
函数的 constructor 是不稳定的,当重写 prototype 后,
原有的 constructor 引用会丢失,constructor 会默认为 Object

4.使用 toString 判断
toString() 是 Object 的原型方法,该方法默认返回当前对象的 [[Class]] 。
这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。
对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。
而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。

Object.prototype.toString.call(undefined) ===  '[object Undefined]'
Object.prototype.toString.call(null) ===  '[object Null]'
Object.prototype.toString.call(123) === '[object Number]'

5.JQuery 提供的 jquery.type()
返回说明操作数的字符串

jQuery.type(123) === "number"
jQuery.type(undefined) === "undefined"
jQuery.type(null ) === "null "
Query.type(new Date()) === "date"
jQuery.type(new Error()) === "error"

追问:null 和 undefined 有啥区别?

null:是 Null类型,表示一个 空对象指针 或 尚未存在的对象
即该处不应该有值,使用typeof运算得到 object ,是个特殊对象值,转为数值为 0
也可以理解是表示程序级的、正常的或在意料之中的值的空缺

  1. 作为函数的参数,表示该函数的参数不是对象
  2. 作为对象原型链的终点
    注意:null 不是一个对象,但 typeof null === object 原因是不同的对象在底层都会表示为二进制,在 JS 中如果二进制的前三位都为 0,就会被判断为object类型,null 的二进制全为 0,自然前三位也是 0,所以 typeof null === 'objcet'

undefined:是Undefined 类型,表示一个 的原始值 或 缺少值,
即此处应该有一个值,但还没有定义,使用 typeof undefined === 'undefined',转为数值为 NaN
它是在 ECMAScript 第三版引入的预定义全局变量,为了区分空指针对象未初始化的变量
也可以理解是表示系统级的出乎意料的类似错误的值的空缺
1.变量被声但没有赋值时
2.调用函数时,应该提供的参数没有提供时
3.对象没有赋值的属性时,属性值为 undefined
4.函数没有返回值时,默认返回值为 undefined

追问: JS 有哪些内置对象

数据封装类对象:Object、Array、Boolean、Number、String
其他对象:Function、Arguments、Math、Date、RegExp、Error

追问:说说你对原型和原型链的理解

原型: 每一个构造函数都会自动带一个 prototype 属性,是个指针,指向一个对象,就是 原型对象
原型对象 上默认有一个属性constructor ,也是个指针,指向构造函数本身

  • 优点:原型对象上所有的 属性 和 方法 都能被构造函数的 实例对象 共享访问。
  • 缺点:多个实例对引用类型的操作会被篡改。
    因为每次实例化,引用类型的数据都指向同一个地址,所以它们 读/写 的是同一个数据,当一个实例对其进行操作,其他实例的数据就会一起更改( 这也是 Vue 中 data 为什么是一个函数的原因 )。

原型链: 每个实例对象都有一个原型__proto__,这个原型还可以有它自己的原型,以此类推,形成一个链式结构即原型链
每个构造函数都有一个原型对象prototype,原型对象上包含一个指向构造函数的指针 constructor
而每个实例都包含着一个指向原型对象的内部指针 __proto__
可以通过内部指针 __proto__访问到原型对象,原型对象通过 constructor 找到构造函数。
如果 A对象 在 B 对象的原型链上,可以说它们是 B对象继承了 A对象。
原型链作用:如果试图访问对象的某个属性,会首先在 对象内部 寻找该属性,直至找不到,然后才在该对象的原型里去找这个属性,以此类推。

new 关键字创建一个实例都做了什么?

1.像普通对象一样,形成自己的私有作用域( 形参赋值,变量提升 )
2.创建一个新对象,将 this 指向这个新对象( 构造函数的作用域赋给新对象 )
3.执行构造函数中的代码,为这个新对象添加属性、方法
4.返回这个新对象( 新对象为构造函数的实例 )
手写一个 new 原理如下:

function myNew(fn, ...arg){
    // 创建一个对象,让它的原型链指向 fn.prototype
    
    // 普通方法
    // let obj = {};
    // obj.__proto__ = fn.prototype;
    
    // 使用 Object.create([A对象]):创建一个空对象 obj,并让 obj.__proto__ 等于 A对象
    let obj = Object.create(fn.prototype);

    fn.call(obj, ...arg);
    return obj;
}

可以用 instanceof 测试构造函数的prototype属性是否出现在实例对象的原型链中
也可以用 obj.hasOwnProperty(prop)测试对象自身属性中是否具有指定的属性

追问:call / apply / bind 有啥区别

都是替换函数中不想要的this
callapply 是临时的且立即执行,
bind 是永久绑定不立即执行,返回一个新函数,需要时再去执行这个新函数。

call: call( thisObj, obj1, obj2... )
要求传入函数的参数必须单独传入

apply: apply(t hisObj, [argArray] )
要求传入函数的参数必须放入数组中整体传入
apply会将数组打散为单个参数值分别传入

bind: 永久绑定函数中的 this,作用如下:

1.创建一个和原函数功能完全一样的新函数.
2.将新函数中的 this 永久绑定为指定对象
3.将新函数中的部分固定参数提前永久绑定

说说 ES6、ES7、ES8 的新特性

ES6的特性:

1.类(class)
2.模块化(Module)导出(export)导入(import)
3.箭头(Arrow)函数
4.函数参数默认值
5.模板字符串
6.延展操作符(Spread operator) 和 剩余运算符(rest operator)
7.ES6中允许我们在设置一个对象的属性的时候不指定属性名
8.Promise 异步编程的解决方案
9.支持 let 与 const 块级作用域

ES7的特性

1.includes() 函数用来判断一个数组是否包含一个指定的值,返回true / false
2.指数操作符在ES7中引入了指数运算符,具有与Math.pow(..)等效的计算结果

ES8的特性

1.加入了对 async/await 的支持,也就我们所说的异步函数
2.Object.values() 是一个与 Object.keys()类似的新函数,但返回的是 Object 自身属性的所有值,不包括继承的值
3.Object.entries() 函数返回一个给定对象自身可枚举属性的键值对的数组
4.String.padStart(targetLength,[padString])String.padEnd(targetLength,padString])
5.Object.getOwnPropertyDescriptors() 函数用来获取一个对象的所有自身属性的描述符,如果没有任何自身属性,则返回空对象。

require 和 import 区别

import 和 require都是被模块化所使用。

1.遵循规范

  • require 是AMD规范引入方式
  • import是es6的语法标准,如要兼容浏览器的话必须转化成es5的语法

2.调用时间

  • require是运行时调用,所以require理论上可以运用在代码的任何地方
  • import是编译时调用,所以必须放在文件开头

3.本质

  • require是赋值过程,其实require的结果就是对象、数字、字符串、函数等,再把require的结果赋值给某个变量
  • import是解构过程,但是目前所有的引擎都还没有实现import,我们使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语法会被转码为require

4.性能

  • require的性能相对于import稍低,因为require是在运行时才引入模块并且还赋值给某个变量
  • import只需要依据import中的接口在编译时引入指定模块所以性能稍高

追问:Es6 Module 和 Common.js 的区别

CommonJS
  • 对于基本数据类型,属于复制,会被模块缓存。可在另一个模块可以对该模块输出的变量重新赋值。
  • 对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
  • 当使用 require 命令加载某个模块时,就会运行整个模块的代码。
  • common.js 同一个模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载就返回第一次运行的结果,除非手动清除系统缓存。
  • 循环加载时,属于加载时执行。即脚本代码在 require 的时候,就会全部执行。一旦出现某个模块被 "循环加载",就只输出已经执行的部分,还未执行的部分不会输出
ES6 Module 模块

ES6 模块中的值属于动态只读引用。

  • 只读:不允许修改引入变量的值,import 的变量是只读的( 包括 基本/复杂 数据类型 )。当模块遇到 import 命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
  • 动态:原始值发生变化,import 加载的值也会发生变化( 包括 基本/复杂 数据类型)。
    循环加载时,ES6 模块是动态引用( 只要两个模块之间存在某个引用,代码就能执行 )。
综上:
  • common.js 是 module.exports / exports 导出,require 导入;ES6 则是 export 导出,import 导入。
  • common.js 是运行时加载模块,ES6 是在静态编译期间就确定模块的依赖。
  • ES6 在编译期间会将所有 import 提升到顶部,common.js 不会提升 require。
  • common.js 导出的是一个值拷贝,会对加载结果进行缓存,一旦内部再修改这个值,则不会同步到外部。ES6 是导出的一个引用,内部修改可以同步到外部。
  • 两者的循环导入的实现原理不同,common.js 是当模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。ES6 模块是动态引用,如果使用 import 从一个模块加载变量(即import foo from 'foo'),那些变量不会被缓存,而是成为一个指向被加载模块的引用。
  • common.js 中顶层的 this 指向这个模块本身,而 ES6 中顶层 this 指向 undefined。

事件委托是什么,原理是什么

事件委托: 利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
原理:利用事件的 冒泡原理
事件冒泡:就是事件从最深的节点开始,然后逐步向上传播事件。

作用:
  • 提高性能:每一个函数都会占用内存空间,只需添加一个事件处理程序代理所有事件,所占用的内存空间更少;
  • 动态监听:使用事件委托可以自动绑定动态添加的元素,即新增的节点不需要主动添加也可以具有和其它元素一样的事件。
如何 阻止冒泡 和 默认事件
停止冒泡: 
window.event ? window.event.cancelBubble = true : e.stopPropagation();
阻止默认事件: 
window.event ? window.event.returnValue = false : e.preventDefault();

追问:说前端中的事件流

事件发生时在元素节点之间按照特定的顺序传播的过程叫做DOM事件流
共分为三大阶段:

捕获阶段(事件从 Document 节点 自上而下 向目标节点传播的阶段)
目标阶段(真正的目标节点正在处理事件的阶段)
冒泡阶段(事件从目标节点 自下而上 向 Document 节点传播的阶段)

事件冒泡:从事件源逐级向上传播到 DOM 最顶层节点的过程。
事件捕获:从 DOM 最顶层节点逐级向下传播到事件源的过程。

追问:说说事件队列

JavaScript语言的一大特点就是 单线程,同一个时间只能做一件事。

作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。这决定了它只能是 单线程,否则会带来很复杂的同步问题。比如 JavaScript 同时有两个线程,一个线程在某个 DOM 节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

为了利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作 DOM。所以,这个新标准并没有改变 JavaScript单线程 的本质。

任务队列的本质

  • 所有 同步任务 都在 主线程 上执行,形成一个执行栈(execution context stack)。
  • 主线程之外,还有一个 任务队列(task queue)。
    只要 异步任务 有了运行结果,就在 任务队列 之中放置一个事件。
  • 等 执行栈 中的所有同步任务执行完毕,系统就会读取 任务队列,看看里面有哪些事件。
    哪些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  • 主线程不断重复上面的第三步。

主线程(执行栈)和 任务队列 先进先出 的通信称为 事件循环( Event Loop )
主要分为:
宏任务(macro-task):DOM事件绑定,定时器,Ajax回调
微任务(micro-task):Promise,MutationObserver (html5新特性)
事件循环机制:主线程 =>所有微任务 ->宏任务
先进先执行,如果里面有微任务,则下一步先执行微任务,否则继续执行宏任务

setTimeout()
将事件插入到了事件队列,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。
当主线程时间执行过长,无法保证回调会在事件指定的时间执行。
浏览器端每次setTimeout 会有 4ms 的延迟,当连续执行多个 setTimeout,有可能会阻塞进程,造成性能问题。
setImmediate()
事件插入到事件队列尾部,主线程和事件队列的函数执行完成之后立即执行。和 setTimeout(fn,0) 的效果差不多。

追问:说说堆栈

栈内存 一般储存 基础数据类型,遵循 先进后出后进先出 的原则,大小固定并由系统自动分配内存空间,运行效率高有序存储
中的 DOM render,ajax,setTimeout,setInterval会依次进入到队列中,当栈中代码执行完毕后,再将队列中的事件放到执行栈中依次执行

内存 一般储存 引用数据类型,JavaScript 不允许直接访问 堆内存 中的位置,需要从 栈中 获取该对象的地址引用/指针,再从 堆内存 中获取数据。存储值大小不定,可动态调整,主要用来存放对象。空间大,但是运行效率相对较低无序存储可根据引用直接获取

说下代码执行结果

let obj = {}, a = 0, b = '0';
obj[a] = 123;
obj[b] = 456;
console.log(obj); // {0: 456} 

对象存在 堆 中,数字属性 和 字符串属性相等
let obj = {}, a = Symbol(1), b = Symbol(1);
obj[a] = 123;
obj[b] = 456;
console.log(obj); // {Symbol(1): 123, Symbol(1): 456}

Symbol 表示独一无二的值,即 Symbol(1) != Symbol(1)
let obj = {}, a = {name: '张三'}, b = {name: '李四'};
obj[a] = 123;
obj[b] = 456;
console.log(obj); // {[object Object]: 456}

把对象作为另一个对象的属性时,会 调用 toString 转换为字符串

追问:对象和数组有啥区别

对象:是包含已命名的值的无序集合,也被称为关联数组
数组:是包含已编码的值的有序集合

  • 创建方式不同,数组是[] / new Array,对象是{} / new Object。
  • 调用方式不同,数组是 arr[下标],对象是 obj.加属性名 / [属性名]。
  • 数组是有序数据的集合,对象是无序。
  • 数组的数据没有名称,只有下标,对象的数据需要指定名称。
  • 数组的元素可以重复,对象的属性是唯一的。
  • 数组的有长度,而对象没有。

追问:数组常用的操作方法有哪些

操作数组:push,splice,join,concat
遍历数组:map,forEach,reduce
筛选数组:filter,some,find,findIndex

追问:如何快速合并两个数组?

(a). arrA.concat(arrB)
(b). Array.prototype.push.apply(arrA,arrB);
(c). Array.prototype.concat.apply(arrA,arrB);
(d). Array.prototype.concat.call(arrA,arrB);
(e). 数组转成字符串拼接在切割成数组, 或者是循环其中一个数组等...
性能自测对比:
Array.prototype.concat.call > Array.prototype.concat.apply > concat > Array.prototype.push.apply

追问:map 和 forEach 有何区别

相同点:

  • 都是循环遍历数组中的每一项

  • forEach 和 map方法里每次执行匿名函数都支持3个参数,
    参数分别是item(当前每一项),index(索引值),arr(原数组)

  • 匿名函数中的 this都是指向 window( 在 Vue 中指向 Vue 实例)
    不同点:

  • map() 返回一个新数组,原数组不会改变,可链式调用

  • forEach() 返回值为 undefined,可链式调用
    场景:

如只是单纯的遍历可用 forEach()
如操作原数组得到新数组可用 map()

追问:什么是数组扁平化,实现扁平化的方法有哪些?

数组扁平化:一个多维数组变为一维数组,方法如下:
1.flat( ES 6)
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

let newArray = arr.flat([depth]);
depth值可选: 指定要提取嵌套数组的结构深度,默认值为 1,不确定层级也可写 `Infinity`。

2.reduce

function flatten(arr) {  
    return arr.reduce((result, item)=> {
        return result.concat(Array.isArray(item) ? flatten(item) : item);
    }, []);
}

3.String & split

function flatten(arr) {
    return arr.toString().split(',').map(function(item) {
        return Number(item);
    })
} 

4.join & split

function flatten(arr) {
    return arr.join(',').split(',').map(function(item) {
        return parseInt(item);
    })
}

5.扩展运算符

[].concat(...[1, 2, 3, [4, 5]]);  // [1, 2, 3, 4, 5]

也可以做一个遍历,若 arr 中含有数组则使用一次扩展运算符,直至没有为止,如下:
扩展运算符每次只能展开一层数组

function flatten(arr) {
    while(arr.some(item=>Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}

6.递归

function flatten(arr) { 
  var res = [];
  arr.map(item => {
    if(Array.isArray(item)) {
      res = res.concat(flatten(item));
    } else {
      res.push(item);
    }
  }); 
  return res;
}

追问:说说缓存 SessionStorage,LocalStorage,Cookie

sessionStorage 是会话级别存储,只要会话结束关闭窗口,sessionStorage 立即被销毁。
localStorage 是持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。
sessionStroagelocalStroage 存储大小可以达到 5M,不能和服务器做交互。
cookie 的数据会始终在同源http请求中携带,在浏览器和服务器之间来回传递。单个cookie 不能超过4K,只在设置的 cookie 过期时间之前有效,即使窗口关闭或浏览器关闭 。很多浏览器都限制一个站点最多保存20个Cookie

说说深拷贝 和 浅拷贝

浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。如果其中一个对象改变了这个地址,就会影响到另一个对象。

  • 直接用=赋值
  • Object.assign
    只是在根属性(对象的第一层级)创建了一个新的对象,但是对于属性的值是仍是对象的话依然是浅拷贝。
    Object.assign 还有一些注意的点是:
    (1)不会拷贝对象继承的属性
    (2)不可枚举的属性
    (3)属性的数据属性/访问器属性
    (4)可以拷贝Symbol类型
  • for in 循环只遍历第一层

深拷贝:将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

  • 用 JSON.stringify 把对象转换成字符串,再用 JSON.parse 把字符串转换成新的对象
    注意:属性值为函数时该属性会丢失,为正则时会转为空对象,为new Date()时会转为字符串
  • 采用递归去拷贝所有层级属性
  • 用 Slice 实现对数组的深拷贝
  • 使用扩展运算符实现深拷贝
// 递归算法实现深克隆
function deepClone(obj){
  if(obj === null) return null;
  if(typeof obj !=='object') return obj;
  if(obj instanceof RegExp) return new RegExp(obj);
  if(obj instanceof Date) return new Date(obj);

  // 克隆的结果和之前保持相同的所属类
  let newObj = new obj.constructor;
  for(let key in obj){
    if(obj.hasOwnProperty(key)){
        newObj[key] = deepFn(obj[key]);
    }
  }
  return newObj
}

.说说 DOM 和 BOM

ECMAScript (核心) : 描述了 JS 的语法 和 基本对象。
文档对象模型 (DOM): 处理 网页内容 的方法和接口。
W3C 的标准( 所有浏览器公共遵守的标准 )
浏览器对象模型 (BOM): 与 浏览器交互 的方法和接口。
各个浏览器厂商根据 DOM 在各自浏览器上的实现( 不同厂商之间实现存在差异 )

DOM 的 API :

节点创建型 API:

document.createElement(),document.createTextNode(),parent.cloneNode(true)
document.createDocumentFragment() 创建文档片段,解决大量添加节点造成的回流问题

页面修改型 API:

parent.appendChild(child),parent.removeChild(child)
parent.replcaeChild(newChild,oldChild)
parent.insertBefore(newNode, referenceNode)

节点查询型 API:

document.getElementById()
document.getElementsByTagName() 返回即时的 HTMLCollection 类型
document.getElementsByName() 根据指定的 name 属性获取元素,返回即时的 NodeList
document.getElementsByClassName() 返回即时的 HTMLCollection
document.querySelector() 获取匹配到的第一个元素,采用的是深度优先搜索
docuemnt.querySelectorAll() 返回非即时的 NodeList,也就是说结果不会随着文档树的变化而变化

节点关系型 API:

父关系型:
node.parentNode()
兄弟关系型:
node.previouSibling() 返回节点的前一个节点(包括元素节点,文本节点,注释节点)
node.previousElementSibling() 返回前一个元素节点
node.nextSibling() 返回下一个节点
node.nextElementSibling() 返回下一个元素节点

子关系型

parent.childNodes() 返回一个即时的NodeList,包括了文本节点和注释节点
parent.children() 一个即时的HTMLCollection,子节点都是Element
parent.firsrtNode(),parent.lastNode(),hasChildNodes()

元素属性型 API:

element.setAttribute(“name”,“value”) 为元素添加属性
element.getAtrribute(“name”) 获取元素的属性

元素样式型 API:

window.getComputedStyle(element) 返回一个CSSStyleDeclaration,可以从中访问元素的任意样式属性。
element.getBoundingClientRect() 返回一个DOMRect对象,里面** 包括了元素相对于可视区的位置 top,left**,以及元素的大小,单位为纯数字。可用于判断某元素是否出现在了可视区域

BOM的 API :

  • location对象
    .href、.search、.hash、.port、.hostname、pathname
  • history对象
    .go(n)(前进或后退指定的页面数)、history.back(后退一页)、.forward(前进一页)
  • navigator对象
    navigator:包含了用户浏览器的信息
    navigator.userAgent:返回用户代理头的字符串表示(就是包括浏览器版本信息等的字符串)
    navigator.cookieEnabled:返回浏览器是否支持(启用) cookie

window对象方法:

  • alert() -- 显示带有一段消息和一个确认按钮的警告弹出框。
  • confirm() -- 显示带有一段消息以及确认按钮和取消按钮的警告弹出框。
  • prompt() -- 显示带有一段消息以及可提示用户输入的对话框和确认,取消的警告弹出框。
  • open() -- 打开一个新的浏览器窗口或查找一个已命名的窗口。
  • close() -- 关闭浏览器窗口。
  • setInterval() -- 按照指定的周期(以毫秒计)来调用函数或计算表达式。每隔多长时间执行一下这个函数
  • clearInterval() -- 取消由 setInterval() 设置的 timeout。
  • setTimeout() -- 在指定的毫秒数后调用函数或计算表达式。
  • clearTimeout() -- 取消由 setTimeout() 方法设置的 timeout。
  • scrollTo() -- 把内容滚动到指定的坐标。

.js 延迟加载的方式有哪些?

  • fer
    会告诉浏览器立即下载,但延迟整个页面都解析完毕之后再执行
    按顺序依次执行
  • async
    不让页面等待脚本下载和执行,从而异步加载页面其他内容。
    将会在下载后尽快执行,不能保证脚本会按顺序执行( 在onload 事件之前完成 )。
  • 动态创建DOM方式(创建script,插入到DOM中,加载完毕后callBack)
  • 使用 setTimeout 延迟方法
  • 让 JS 最后加载

.说说跨域

跨域:指一个域下的文档或脚本试图去请求另一个域下的资源,由于浏览器同源策略限制而产生。
同源策略: 同协议+同端口+同域名。即便两个不同的域名指向同一个ip地址,也非同源。
如果缺少了同源策略,浏览器很容易受到XSS、CSFR 等攻击。

解决方案:

  • Vue 配置代理类proxy
  • jsonp 利用标签没有跨越的特点,单只能实现get请求不能post请求
  • CORS 跨域资源共享,只服务端设置Access-Control-Allow-Origin即可,前端无须设置
  • nginx代理转发
  • window.name + iframe跨域: 通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域
  • location.hash + iframe: a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
  • document.domain + iframe跨域(仅限主域相同,子域不同的跨域应用场景):两个页面都通过js强制设置document.domain为基础主域,就实现了同域;

.for in 和 for of 的区别

-for in遍历的是数组的索引,在for in
(1).for inindex 索引为字符串型数字,不能直接进行几何运算
(2). for in 遍历顺序有可能不是按照实际数组的内部顺序
(3). 因为for in是遍历可枚举的属性,也包括原型上的属性( 如不想遍历原型上的属性,可通过 hasOwnProperty 判断某个属性是属于原型 还是 实例上 )。

  • for of 遍历的是数组的元素值
    for of只是遍历数组的内部,不会遍历原型上的属性和索引
    也可以通过ES5的 Object.keys(obj) 来获取实例对象上的属性组成的数组

一般是使用for in 来遍历对象,for of 遍历数组

.instanceof 的原理是什么?

function myInstanceof(left, right) {
  let prototype = right.prototype
  left = left.__proto__
  while (true) {
    if (left === null || left === undefined)
      return false
    if (prototype === left)
      return true
    left = left.__proto__
  }
}
思路:
首先获取类型的原型
然后获得对象的原型
然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null

. setInterval 存在的问题

定时器的代码执行部分不断的被调入任务队列中,如果定时器的执行时间比间隔时间长,最终可能导致定时器堆叠在一起执行。

js 引擎为了解决这个问题,采用的方式是若任务队列中存在这个定期器,则不会将新的定时器放入任务队列,这样做的弊端是可能导致某些间隔被跳过。

解决方法:循环调用setTimeout来实现setInterval:(即用setTimeout来实现setInterval

 setTimeout(function fn(){
    ...
    setTimeout(fn,delay)
},delay)

列举几条 JS 的基本代码规范

  • 变量和函数命名要见名知意
  • 当命名对象、函数和实例时使用驼峰命名规则
  • 请使用 === / !== 来值的比较
  • 对字符串使用单引号 ''(因为大多时候我们的字符串。特别html会出现")
  • switch 语句必须带有 default 分支
  • 语句结束一定要加分号
  • for 循环必须使用大括号
  • 使用 /*.../ 进行多行注释,包括描述,指定类型以及参数值和返回值

.什么是作用域链(scope chain)

作用域链: 由各级作用域对象连续引用,形成的链式结构

函数的声明周期:

  • 程序开始执行前: 程序会创建全局作用域对象window
  • 定义函数时
    在window中创建函数名变量引用函数对象
    函数对象的隐藏属性scope指回函数来自的全局作用域对象window
  • 调用函数时
    创建本次函数调用时使用的AO对象
    在AO对象中添加函数的局部变量
    设置AO的隐藏属性parent 指向函数的祖籍作用域对象。——执行时,如果AO中没有的变量可延parnet向祖籍作用域对象找。
  • 函数调用后
    函数作用域对象AO释放
    导致AO中局部变量释放

作用

  • 保存所有的变量
  • 控制变量的使用顺序: 先用局部,局部没有才延作用域链向下查找。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容