1 js数据类型划分?
javaScript属于弱语言类型,当前数据类型是由值决定的。
- 基本数据类型
- Number 数字类
- String 字符串类
- Boolean 布尔类
- null 空,没有,访问一个不存在的对象,返回值就是null
- undefined 未定义,没有进行赋值
- symbol(es6新增)独一无二的数据类型
- 复杂数据类型(引用数据类型)
- Object 对象类
- Array 数组类
- function 函数
2 基本数据类型与引用数据类型有什么区别?
(1)两者作为函数的参数进行传递时:
基本数据类型传入的是数据的副本,原数据的更改不会影响传入后的数据。
引用数据类型传入的是数据的引用地址,原数据的更改会影响传入后的数据。
(2)两者在内存中的存储位置:
基本数据类型存储在栈中。
引用数据类型在栈中存储了指针,该指针指向的数据实体存储在堆中。
3 逻辑运算符用过吗?
&& 与 || 或 !非
&&: 表示且 的关系,都为真才为真,一假即假
|| :表示或的关系,都为假才为假,一真即真
!: 取反 先将当前数据转为布尔值再进行取反
4 你都知道那些判断数据类型的方法?
(1)利用 typeof 可以判断数据的类型;
(2)A instanceof B 可以用来判断 A 是否为 B 的实例,但它不能检测 null 和 undefined;
(3)B.constructor == A 可以判断 A 是否为 B 的原型,但 constructor 检测 Object 与 instanceof 不一样,还可以处理基本数据类型的检测。
(4)Object.prototype.toString.call() 是最准确最常用的方式。
5 深浅拷贝有什么区别?通过的什么方法?
浅拷贝只复制指向某个对象的指针,而不复制对象本身。
浅拷贝的实现方式有:
(1)Object.assign():需注意的是目标对象只有一层的时候,是深拷贝;
(2)扩展运算符;
深拷贝就是在拷贝数据的时候,将数据的所有引用结构都拷贝一份。
深拷贝的实现方式有:
(1)手写遍历递归赋值;
(2)结合使用JSON.parse()和JSON.stringify()方法。
6 let const var 有什么区别?
9 什么是执行上下文与执行栈?区别是什么?
变量或函数的执行上下文,决定了它们的行为以及可以访问哪些数据。每个上下文都有一个关联的变量对象, 而这个上下文中定义的所有变量和函数都存在于这个对象上(如DOM中全局上下文关联的便是window对象)。
每个函数调用都有自己的上下文。当代码执行流进入函数时,函数的上下文被推到一个执行栈中。在函数执行 完之后,执行栈会弹出该函数上下文,在其上的所有变量和函数都会被销毁,并将控制权返还给之前的执行上 下文。 JS的执行流就是通过这个执行栈进行控制的。
区别:
(1)函数的执行上下文只在函数被调用时生成,而其作用域在创建时已经生成;
(2)函数的作用域会包含若干个执行上下文(有可能是零个,当函数未被调用时)。
10 什么是作用域以及作用域链?
作用域可以理解为一个独立的地盘,可以理解为标识符所能生效的范围。作用域最大的用处就是隔离变 量,不同作用域下同名变量不会有冲突。ES6中有全局作用域、函数作用域和块级作用域三层概念。
当一个变量在当前块级作用域中未被定义时,会向父级作用域(创建该函数的那个父级作用域)寻找。
如果父级仍未找到,就会再一层一层向上寻找,直到找到全局作用域为止。这种一层一层的关系,就是作用域链 。
11 分析下this的指向情况?
在对象内部的方法中使用对象内部的属性是一个非常普遍的需求。但是 JavaScript 的作用域机制并不支持这一点,基于这个需求,JavaScript 又搞出来另外一套 this 机制。 this存在的场景有三种全局执行上下文和函数执行上下文和eval执行上下文,eval这种不讨论。在全局执行环境中无论是否在严格模式下,(在任何函数体外部)`this` 都指向全局对象。在函数执行上下文中访问this,函数的调用方式决定了 `this` 的值。在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window,通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身。 普通函数this指向:当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身。new 关键字构建好了一个新对象,并且构造函数中的 this 其实就是新对象本身。嵌套函数中的 this 不会继承外层函数的 this 值。 箭头函数this指向:箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数。 加分回答 箭头函数因为没有this,所以也不能作为构造函数,但是需要继承函数外部this的时候,使用箭头函数比较方便 var myObj = { name : "闷倒驴", showThis:function(){ console.log(this); // myObj var bar = ()=>{ this.name = "王美丽"; console.log(this) // myObj } bar(); } }; myObj.showThis(); console.log(myObj.name); // "王美丽" console.log(window.name); // ''
(1)全局作用域中的函数:其内部 this 指向 window:
var a = 1;function fn() { console.log(this.a);}fn(); //输出1
(2)对象内部的函数:其内部 this 指向对象本身:
var a = 1;var obj = { a: 2, fn: function () { console.log(this.a); },};obj.fn(); //输出2
(3)构造函数:其内部 this 指向生成的实例:
function createP(name, age) { this.name = name; //this.name指向P this.age = age; //this.age指向P}var p = new createP("小臂猿", 20);
(4)由 apply、call、bind 改造的函数:其 this 指向第一个参数:
function add(c,d){ return this.a + this.b + c + d}var o = {a:1,b:2)add.call(o,5,7) //输出15
(5)箭头函数:箭头函数没有自己的 this,看其外层的是否有函数,如果有,外层函数的 this 就是内部箭头函数的 this,如果没有,则 this 是 window。
11 如何改变this的指向?
可以使用 apply、call、bind 方法改变 this 指向(并不会改变函数的作用域)。比较如下:
(1)三者第一个参数都是 this 要指向的对象,也就是想指定的上下文,上下文就是指调用函数的那个对象(没有就指向全局 window);
(2)bind 和 call 的第二个参数都是数组,apply 接收多个参数并用逗号隔开;
(3)apply 和 call 只对原函数做改动,bind 会返回新的函数(要生效还得再调用一次)
12 怎么理解同步异步?
同步:按照代码书写顺序一一执行处理指令的一种模式,上一段代码执行完才能执行下一段代码。
异步:可以理解为一种并行处理的方式,不必等待一个程序执行完,可以执行其它的任务。
JS之所以需要异步的原因在于JS是单线程运行的。常用的异步场景有:定时器、ajax请求、事件绑定。
13 什么是闭包?
闭包就是引用了其他函数作用域中变量的函数,这种模式通常在函数嵌套结构中实现。里面的函数可以访问外面函数的变量,外面的变量的是这个内部函数的一部分。
14 闭包的作用是什么?
(1)读取函数内部的变量
(2)让这些变量的值始终保持在内存中。不会再f1调用后被自动清除。
(3)方便调用上下文的局部变量。利于代码封装。
原因:f1是f2的父函数,f2被赋给了一个全局变量,f2始终存在内存中,f2的存在依赖f1,因此f1也始终 存在内存中,不会在调用结束后,被垃圾回收机制回收。
15 那闭包的应用场景呢?
(1)函数防抖
比如要缩放窗口 触发onresize 事件 需要在这时候做一件事情,但是我们希望拖动的时候只触发一次,比如
window.onresize = function(){
console.log('onresize')//只想触发一次
}
一般方法vs闭包
window.onresize = function(){
debounce(fn,1000)
}
var fn = function(){ console.log('fn')
}
var time = ''
function debounce(fn,timeLong){
if(time){
clearTimeout(time)
time = ''
}
time =setTimeout(function(){
fn()
},timeLong)
}
window.onresize = debounce(fn,500)
function debounce(fn){
var timer = null
return function(){
if(timer){ //timer第一次执行后会保存在内存里 永远都是执行器 直到最后被触发
clearTimeout(timer)
timer = null
}
timer = setTimeout(function(){
fn()
},1000)
}
}
var fn = function(){
console.log('fn')
}
(2)使用闭包设计单例模式
class CreateUser {
constructor(name) {
this.name = name;
this.getName();
}
getName() {
return this.name;
}
}
// 代理实现单例模式
var ProxyMode = (function() {
var instance = null;
return function(name) {
if(!instance) {
instance = new CreateUser(name);
}
return instance;
}
})();
// 测试单体模式的实例
var a = ProxyMode("aaa");
var b = ProxyMode("bbb");
// 因为单体模式是只实例化一次,所以下面的实例是相等的
console.log(a === b); //true
(3)为多个组件独立属性
假如我现在要在页面中使用echarts画6个线状图,需要6个容器
需要为每个容器元素声明一个独立id,不然会混乱
constructor(){
this.state = {id: "EchartsLine"+Util.clourse()};
}
componentDidMount() {
this.myEChart =echarts.init(document.getElementById(this.state.id));//不同id
}
<div
id={this.state.id}
className='echarts-line'>
</div>
clourse(){
let clourse = (function(){
var a = 1;
return function(){
return a++;
}
})(this);
this.clourse = clourse;
}
//使用数字命名 不用害怕被篡改
(4)设置私有变量
let _width = Symbol();
class Private {
constructor(s) {
this[_width] = s
}
foo() {
console.log(this[_width])
}
}
var p = new Private("50");
p.foo();
console.log(p[_width]);//可以拿到
//赋值到闭包里
let sque = (function () {
let _width = Symbol();
class Squery {
constructor(s) {
this[_width] = s
}
foo() {
console.log(this[_width])
}
}
return Squery
})();
let ss = new sque(20);
ss.foo();
console.log(ss[_width])
(5)拿到正确的值
for(var i=0;i<10;i++){
setTimeout(function(){
console.log(i)//10个10
},1000)
}
for(var i=0;i<10;i++){
((j)=>{
setTimeout(function(){
console.log(j)//1-10
},1000)})(i)
}
16 什么是原型·原型链?
原型:JS 声明构造函数(用来实例化对象的函数)时,会在内存中创建一个对应的对象,这个对象就是原函数的原型。构造函数默认有一个 prototype 属性,prototype 的值指向函数的原型。同时原型中也有一个 constructor 属性,constructor 的值指向原函数。
通过构造函数实例化出来的对象,并不具有 prototype 属性,其默认有一个proto属性,proto的值指向构造函数的原型对象。在原型对象上添加或修改的属性,在所有实例化出的对象上都可共享。
当在实例化的对象中访问一个属性时,首先会在该对象内部寻找,如找不到,则会向其proto指向的原型中寻找,如仍找不到,则继续向原型中proto指向的上级原型中寻找,直至找到或 Object.prototype 为止,这种链状过程即为原型链。
17 何为防抖与节流?
防抖和节流都是防止短时间内高频触发事件的方案。
防抖的原理是:如果一定时间内多次执行了某事件,则只执行其中的最后一次。
节流的原理是:要执行的事件每隔一段时间会被冷却,无法执行。
应用场景有:搜索框实时搜索,滚动改变相关的事件。
//@fn: 要执行的函数//@delay: 设定的时限//防抖函数function debunce(fn, delay) { let flag = null; return function () { if (flag) clearTimeout(flag); //利用apply改变函数指向,使得封装后的函数可以接收event本身 flag = setTimeout(() => fn.apply(this, arguments), delay); };}//节流函数function throttle(fn, delay) { let flag = true; return function () { if (!flag) return false; flag = false; setTimeout(() => { fn.apply(this, arguments); flag = true; }, delay); };}
18 js是如何实现异步的?
JS 引擎是单线程的,但又能实现异步的原因在于事件循环和任务队列体系。
事件循环:
JS 会创建一个类似于 while (true) 的循环,每执行一次循环体的过程称之为 Tick。每次 Tick 的过程就是查看是否有待处理事件,如果有则取出相关事件及回调函数放入执行栈中由主线程执行。待处理的事件会存储在一个任务队列中,也就是每次 Tick 会查看任务队列中是否有需要执行的任务。
任务队列:
异步操作会将相关回调添加到任务队列中。而不同的异步操作添加到任务队列的时机也不同,如 onclick, setTimeout, ajax 处理的方式都不同,这些异步操作是由浏览器内核的 webcore 来执行的,浏览器内核包含 3 种 webAPI,分别是 DOM Binding、network、timer 模块。
onclick 由 DOM Binding 模块来处理,当事件触发的时候,回调函数会立即添加到任务队列中。setTimeout 由 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。ajax 由 network 模块来处理,在网络请求完成返回之后,才将回调添加到任务队列中。
主线程:
JS 只有一个线程,称之为主线程。而事件循环是主线程中执行栈里的代码执行完毕之后,才开始执行的。所以,主线程中要执行的代码时间过长,会阻塞事件循环的执行,也就会阻塞异步操作的执行。
只有当主线程中执行栈为空的时候(即同步代码执行完后),才会进行事件循环来观察要执行的事件回调,当事件循环检测到任务队列中有事件就取出相关回调放入执行栈中由主线程执行。
19 什么是AJAX?你是如何实现的?
ajax是一种能够实现局部网页刷新的技术,可以使网页异步刷新。
ajax的实现主要包括四个步骤:
(1)创建核心对象XMLhttpRequest;
(2)利用open方法打开与服务器的连接;
(3)利用send方法发送请求;("POST"请求时,还需额外设置请求头)
(4)监听服务器响应,接收返回值。
//1-创建核心对象//该对象有兼容问题,低版本浏览器应使用ActiveXObjectconst xthhp = new XMLHttpRequest();//2-连接服务器//open(method,url,async)xhttp.open("POST","http://localhost:3000",true)//设置请求头xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");//3-发送请求//send方法发送请求参数,如为GET方法,则在open中url后拼接xhttp.send({_id:123})//4-接收服务器响应//onreadystatechange事件,会在xhttp的状态发生变化时自动调用xhttp.onreadystatechange =function(){ //状态码共5种:0-未open 1-已open 2-已send 3-读取响应 4-响应读取结束 if(xhttp.readyState == 4 && xhttp.status == 200){ alert("ajax请求已完成") }}
20 实现异步的方式有哪些?
(1)回调函数模式:将需要异步执行的函数作为回调函数执行,其缺点在于处理复杂逻辑异步逻辑时,会造成回调地狱(回调嵌套层数太多,代码结构混乱);
(2)事件监听模式:采用事件驱动的思想,当某一事件发生时触发执行异步函数,其缺点在于整个代码全部得变为事件驱动模式,难以分辨主流程;
(3)发布订阅模式:当异步任务执行完成时发布消息给信号中心,其他任务通过在信号中心中订阅消息来确定自己是否开始执行;
(4)Promise(ES6):Promise 对象共有三种状态 pending(初始化状态)、fulfilled(成功状态)、rejected(失败状态)。
(5)async/await(ES7):基于 Promise 实现的异步函数;
(6)利用生成器实现。
21 怎么理解Promise对象?
Promise 对象有如下两个特点:
(1)对象的状态不受外界影响。Promise 对象共有三种状态 pending、fulfilled、rejected。状态值只会被异步结果决定,其他任何操作无法改变。
(2)状态一旦成型,就不会再变,且任何时候都可得到这个结果。状态值会由 pending 变为 fulfilled 或 rejected,这时即为 resolved。
Promise 的缺点有如下三个缺点:
(1)Promise 一旦执行便无法被取消;
(2)不可设置回调函数,其内部发生的错误无法捕获;
(3)当处于 pending 状态时,无法得知其具体发展到了哪个阶段。
Pomise 中常用的方法有:
(1)Promise.prototype.then():Promise 实例的状态发生改变时,会调用 then 内部的回调函数。then 方法接受两个参数(第一个为 resolved 状态时时执行的回调,第一个为 rejected 状态时时执行的回调)
(2)Promise.prototype.catch():.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
22 怎么理解微任务宏任务?
宏任务有:script(整体代码)、setTimeout、setInterval、I/O、页面渲染;
微任务有:Promise.then、Object.observe、MutationObserver。
执行顺序大致如下:
主线程任务——>宏任务——>微任务——>微任务里的宏任务——>.......——>直到任务全部完成
23 实现基层的方法有哪些?
//类模板 class Animal { constructor(name){ this.name = name } }//继承类 class Cat extends Animal{//重点。extends方法,内部用constructor+super constructor(name) {super(name); //super作为函数调用时,代表父类的构造函数 }//constructor可省略 eat(){ console.log("eating") } }
//类模板 function Animal(name) { this.name = name; }//添加原型方法 Animal.prototype.eat = function(){ console.log("eating") }function Cat(furColor){ this.color = color ; };//继承类 Cat.prototype = new Animal()//重点:子实例的原型等于父类的实例
function Animal(name){ this.name = name }function Cat(){ Animal.call(this,"CatName")//重点,调用父类的call方法 }
(1)require是CommonJS语法,import是ES6语法;
(2)require只在后端服务器支持,import在高版本浏览器及Node中都可以支持;
(3)require引入的是原始导出值的复制,import则是导出值的引用;
(4)require时运行时动态加载,import是静态编译;
(5)require调用时默认不是严格模式,import则默认调用严格模式.
25 如何理解高阶函数?
JavaScript中的一切都是对象,包括函数。我们可以将变量作为参数传递给函数,函数也是如此。我们调用接受和或返回另一个函数称为高阶函数的函数。
26 跨域的话前面有一篇专门介绍,可以翻看一下
27 请描述一下 cookies,sessionStorage 和 localStorage 的区别?
sessionStorage 用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此 sessionStorage 不是一种持久化的本地存储,仅仅是会话级别的存储。而 localStorage 用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。
web storage 和 cookie 的区别
Web Storage 的概念和 cookie 相似,区别是它是为了更大容量存储设计的。Cookie 的大小是受限的,并且每次你请求一个新的页面的时候 Cookie 都会被发送过去,这样无形中浪费了带宽,另外 cookie 还需要指定作用域,不可以跨域调用。
除此之外,Web Storage 拥有 setItem,getItem,removeItem,clear 等方法,不像 cookie 需要前端开发者自己封装 setCookie,getCookie。但是 Cookie 也是不可以或缺的:Cookie 的作用是与服务器进行交互,作为 HTTP 规范的一部分而存在 ,而 Web Storage 仅仅是为了在本地“存储”数据而生。
28 一次完整的http过程又是什么流程?
基本流程:
a. 域名解析
b. 发起 TCP 的 3 次握手
c. 建立 TCP 连接后发起 http 请求
d. 服务器端响应 http 请求,浏览器得到 html 代码
e. 浏览器解析 html 代码,并请求 html 代码中的资源
f. 浏览器对页面进行渲染呈现给用户
29 在 Javascript 中什么是伪数组?如何将伪数组转化为标准数组?
伪数组(类数组):无法直接调用数组方法或期望 length 属性有什么特殊的行为,但仍可以对真正数组遍历方法来遍历它们。典型的是函数的 argument 参数,还有像调用 getElementsByTagName,document.childNodes 之类的,它们都返回 NodeList 对象都属于伪数组。可以使用 Array.prototype.slice.call(fakeArray)将数组转化为真正的 Array 对象。
function log(){
var args = Array.prototype.slice.call(arguments);
//为了使用 unshift 数组方法,将 argument 转化为真正的数组
args.unshift('(app)');
console.log.apply(console, args);
};
30 你了解优雅降级与渐进增强吗?
渐进增强 progressive enhancement:
针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。
优雅降级 graceful degradation:
一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。
区别:
a. 优雅降级是从复杂的现状开始,并试图减少用户体验的供给
b. 渐进增强则是从一个非常基础的,能够起作用的版本开始,并不断扩充,以适应未来环境的需要
c. 降级(功能衰减)意味着往回看;而渐进增强则意味着朝前看,同时保证其根基处于安全地带
31 那你说说src与href的区别?
href 是指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,用于超链接。
src 是指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求 src 资源时会将其指向的资源下载并应用到文档内,例如 js 脚本,img 图片和 frame 等元素。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也如此,类似于将所指向资源嵌入当前标签内。这也是为什么将 js 脚本放在底部而不是头部。