说一下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
。
也可以理解是表示程序级的、正常的或在意料之中的值的空缺
- 作为函数的参数,表示该函数的参数不是对象
- 作为对象原型链的终点
注意: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
:
call
和 apply
是临时的且立即执行,
bind 是永久绑定不立即执行,返回一个新函数,需要时再去执行这个新函数。
cal
l: 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
是持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。
sessionStroage
和 localStroage
存储大小可以达到 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 in
中 index
索引为字符串型数字,不能直接进行几何运算
(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中局部变量释放
作用
- 保存所有的变量
- 控制变量的使用顺序: 先用局部,局部没有才延作用域链向下查找。