1. Ajax
定义和用法:
AJAX用来与后端进行数据交互, 能够在不刷新页面的情况下,局部更新网页的内容。ajax出现之前的网页,如果需要更新页面内容,必须刷新整个页面。
优点:
1.减轻服务器压力, 按需获取数据, 最大程度的减少冗余请求。
2.异步通信, 局部刷新页面, 减少用户等待时间, 带来更好的用户体验。
3.促进了页面和数据的分离。
缺点:
1.Ajax对外暴露了数据接口, 容易造成一些安全问题,如:sql注入。
2.不能使用浏览器前进后退按钮
AJAX的工作原理:
1.创建XMLHttpRequest对象
var ajax = new XMLHttpRequest();
2.初始化请求:设置请求的类型、请求路径、是否异步(默认异步)
ajax.open('GET', url, true);
3.设置请求头的内容编码类型
ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
4.发送请求
ajax.send(null);
// ajax.send("username=杨&pwd=123456"); // post
5.接收服务器响应数据
ajax.onreadystatechange = function () {
if (ajax.readyState == 4 && (ajax.status == 200 || ajax.status == 304)) {
// …
}
};
2. 什么是跨域,如何实现跨域访问?
跨域是浏览器的同源策略限制,只有在同源的情况下,才允许ajax请求成功。协议、ip、端口任何一个不同,就是不同源,就会造成跨域。
解决跨越:
(1)JSONP跨域:利用script标签的src属性不受浏览器同源策略的限制,传入本地回调方法,服务器返回结果时回调。
(2)跨域资源共享(CORS): 在服务端设置响应头信息,允许浏览器跨域访问。
(3)反向代理
(4)iframe跨域
CORS与JSONP对比:
a、JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
b、使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
c、JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS。
3. get和post的区别,何时使用post?
(1) get是从服务器上获取数据,post是向服务器发送数据。
(2) get是把参数添加到URL中, 在URL中可以看到参数。post把参数放置在HTTP包的body体中, 在路径中看不到, 相对于get更安全。
(3)get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般不受限制。
4.什么是"use strict"?它的好处和坏处分别是什么?
在代码中使用"use strict",代码将按照严格模式解析,如:
1)变量必须声明后使用
2)同一作用域中不能有同名变量
3)增加了一些保留字(比如protected、static和interface)
4)禁止this指向全局对象
5)不能使用arguments.callee,不能使用arguments.caller,不能使用fn.caller
6)fn.arguments不能获取函数调用的堆栈
7)不能删除变量delete prop,只能删除属性delete global[prop]
好处:
消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为;
消除代码运行的一些不安全之处,保证代码运行的安全;
提高编译器效率,增加运行速度;
为未来新版本的Javascript做好铺垫。
坏处:严格模式下,一些代码可能会出现问题。
5.拖拽会用到哪些事件?
ondragstart: 拖拽开始, 执行一次。
ondrag: 拖拽期间在被拖拽元素上连续触发
ondragend: 拖拽结束, 执行一次。
ondragenter: 拖拽进入, 执行一次。
ondragover: 悬停于目标盒子之上,连续触发。
ondragleave: 拖拽离开, 执行一次。
ondrop: 在一个拖动过程中,释放鼠标键时触发此事件, 该事件绑给document, 需要阻止document.ondragover的默认事件。
6.谈谈你对闭包的理解?
(1)闭包可以理解为函数嵌套函数, 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免变量污染,缺点是会常驻内存,延长变量的生命周期,使用不当很容易造成内存泄露。
(2)闭包的三个特性:
a、函数嵌套函数
b、内部函数可以使用外部函数的参数和变量
c、延长参数和变量的生命周期, 容易造成内存泄漏
7.谈谈你this对象的理解?
1.在一般函数方法中, this指代全局对象window。
2.作为对象方法调用, this指代上级对象。
3.作为构造函数调用, this指代new出的对象。
4.在事件中, this指向事件源。
5.箭头函数没有this,它里面的this就是其外层函数的this。
6.总结: 谁调用的函数, 函数中的this就指向谁。
8.浏览器多个标签页之间的通信
BroadcastChannel
广播频道,官方文档里的解释为,用于同源不同页面之间完成通信的功能,在其中某个页面发送的消息会被其他页面监听到
/*
用于同源不同页面之间完成通信的功能:
使用构造函数创建一个实例,可以接收一个string作为频道的name标识。
在其他页面,可以通过new相同的name的BroadcastChannel来使用同一个广播频道。
*/
const bc = new BroadcastChannel("broadcast");
// 监听消息,也可以使用addEventListener来添加"message"监听。
bc.onmessage = (data) => {
console.log("BroadcastChannel data:", data);
};
// 错误也可以绑定监听
bc.onmessageerror = (error) => {
console.warn("BroadcastChannel error:", error);
};
function postMessage() {
// 发送消息
bc.postMessage("BroadcastChannel hello world");
}
/* 关闭,关闭后将无法调用postMessage,如果之后又再需要广播,
则可以重新创建一个相同 name 的 Broadcast Channel
*/
// bc.close();
localStorage
localStorage 是浏览器多个标签共用的存储空间,所以可以用来实现多标签之间的通信(session 是会话级的存储空间,每个标签页都是单独的)
window.addEventListener('storage', () => {
// callback
});
9. 请写出js内存泄漏的问题?
1)移除或替换元素时,若元素绑定的事件还没被移除,就会造成内存泄露, 要先手动移除事件, 再移除或替换dom或者采用事件委托。
<div id="myDiv">
<button id="myBtn">Click me</button>
</div>
<script type="text/javascript">
myBtn.onclick = function () {
// 不加这一句, 就会造成内存泄漏, 或者使用事件委托, 移除子元素时就不会造成内存泄漏.
myBtn.onclick = null;
document.getElementById("myDiv").innerHTML = "123";
}
</script>
2)闭包容易造成内存泄漏,下面的闭包就把变量a泄漏了, 因为函数func的返回值可以多次调用。
function func() {
var a = [1, 2, 3];
return function () {
console.log(a);
}
}
3)意外的全局变量, 下面函数里的变量a的空间就不会被释放掉。
function foo() {
a = [1, 2, 3];
}
4)setInterval定时器忘记被关闭时, 会造成内存泄漏。
10. promise方法的理解和使用?
promise是es6提出的异步处理方案,他有三个状态:pending、resolved、rejected
Promise构造函数是同步执行,.then或.catch是异步执行。
Promise优点:让异步回调变成规范的链式调用,程序流程变很更清楚。
Promise缺点:
1.一旦创建就无法取消
2.pending状态时,不知道在哪个阶段了
3.try {} catch (error) {} 只能捕获同步错误,捕获不了Promise异步代码里的错误
11.eval()的作用?
eval的作用: 把字符串解析成JS代码并运行;应该避免使用eval, 不安全,而且非常耗性能(2次,一次解析成js语句,一次执行)。
eval("2+3"); // 执行加法运算,并返回运算值。
eval("var age = 10"); // 声明一个age变量
由JSON字符串转换为JSON对象的时候可以用eval,var obj = eval('('+ str +')')。
eval作用域问题:
eval("var x=1"); // 相当于 var x = 1;
console.log(x); // 输出1
12.什么是事件委托,事件委托优点是什么?
利用了事件冒泡原理,把事件绑定到父级上,代替子元素执行事件。
优点:
1:减少事件注册,节省内存。
2:简化了dom节点更新时相应事件的更新
缺点:
1:事件委托基于冒泡, 对于不冒泡的事件不支持。
2:层级过多时, 冒泡过程中可能会被某层阻止掉。
13.new操作符具体干了什么呢?
1.创建一个对象
2.将this指向创建的对象
3.使这个对象继承该函数的原型
如何确保你的构造函数只能被 new 调用,而不能被普通调用:
// 使用 instanceof 实现,代码如下:
function Person(firstName, lastName) {
if (!(this instanceof Person)) {
throw new TypeError('Function constructor A cannot be invoked without "new"')
}
this.firstName = firstName;
this.lastName = lastName;
this.fullName = this.firstName + this.lastName;
}
14.暂时性死区
暂时性死区是针对const和let这两个关键字而产生的概念。首先变量提升这个基本概念在js中无法撼动,const和let声明的变量也不例外。和var不同的是,const和let将作用域限制在了块中。在块中,const和let声明的变量已经被分配了内存,但当代码未执行到声明处时,访问变量是会报错的,这就是'暂时性死区'。通俗来说就是该变量虽然已经存在了,但暂时还不能访问,只有等声明变量的那一行代码出现后,才可以访问。
15.什么是作用域?有几种作用域?什么是作用域链?
作用域: 函数和变量的作用范围,有:全局作用域、函数作用域、块级作用域
作用域链:
当代码在一个作用域中执行时,对变量的获取有一个作用域链。它保证了对执行环境中有访问权限的变量和函数进行有序的访问。
16.Es6中箭头函数与普通函数的区别?
1)箭头函数没有自己的this,它里面的this指的是其外层函数的this。
2)箭头函数没有原型对象prototype,不可以当成构造函数使用, 也不可以使用new操作符。
3)箭头函数内部不存在arguments对象, 可以用rest参数来代替。
4)箭头函数不可以使用yield命令, 不能用做generator函数。
5)箭头函数通过let或const关键字声明,不通过function关键字
17.javascript几部分组成?
1.核心(ECMAScript)
2.文档对象模型(DOM)
3.浏览器对象模型(BOM)
18.js判断数据类型方法?
typeof 判断基本数据类型:console.log(typeof 1);
instanceof 判断是否为基于某个构造函数的实例
constructor 返回相对应的构造函数, 可用来判断所有的数据类型
Object.prototype.toString.apply([]) 可用来判断所有的数据类
function type(param) { // 可用来判断所有的数据类型
var str = Object.prototype.toString.call(param);
str = str.split(' ')[1];
str = str.substring(0, str.length - 1);
return str;
}
var a = [];
console.log(a instanceof Array); // true
console.log(a.constructor === Array); // true
console.log([].constructor === Array); // true
console.log(({}).constructor === Object); // true
console.log('a'.constructor === String); // true
function F() {this.a = 1}
const b = new F();
console.log(b instanceof F); // true
console.log(b instanceof Object); // true
console.log(b.constructor === F); // true
console.log(b.constructor === Object); // false
19.undefined和null区别
undefined是个值,当声明变量还未被初始化时,变量的默认值为undefined。
null是个空对象,表示什么都没有,垃圾回收机制会回收掉变量原来占的内存。
typeof null === ‘object’ typeof undefined === ‘undefined’
20.事件冒泡和事件捕获
事件冒泡:从子到父逐级向上触发, 直到document结束。
事件捕获:从根节点到目标节点, 逐级向下触发。
21.Object.is()与原来的比较操作符 "===" 、"==" 的区别?
== 先转换类型再比较,=== 先比较类型, 再比较值。
用 Object.is 进行相等判断时,一般情况下和===的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 认定为是相等的。
22.arguments是什么?arguments是数组还是对象?怎么将arguments转换数组?
在js的函数内可以访问一个特别变量arguments, 这个变量维护着所有传递到这个函数中的参数。arguments不是一个数组, 尽管在语法上它有数组相关的属性length,但它不从 Array.prototype继承,实际上它是一个对象。
转换成数组: Array.prototype.slice.call(arguments);
arguments对象有一个重要的属性callee, 值为当前正执行的函数本身。
23.什么是面向对象?
面向对象是对面向过程的封装, 它是基于对象的概念, 以对象为中心, 以类、封装、继承、多态为构造机制来描述客观世界并设计和构建相应的软件系统,面相对象通俗一点就是模拟现实。面向对象是一种编程思想, 它不是某一种编程语言所特有的。
24.简述一下js原型链继承原理?
原型对象:
每一个函数(箭头函数除外)都有一个prototype属性, 该属性对应的值就是函数的原型对象。构造函数new出来的对象是实例对象, 原型对象里的属性和方法是所有实例对象所公有的,构造函数里面的属性和方法是每个实例对象私有的。原型对象里的属性和方法不属于实例对象, 但实例对象可以使用。
原型链:
每一个实例对象都有一个隐性属性__proto__, 就是原型链, 该属性的值为创建该实例对象的构造函数的原型对象。
原型继承、构造函数继承、组合继承、寄生继承、寄生组合继承
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = { // 原型对象
print() {
console.log('我叫' + this.name + ', 我今年' + this.age + '岁了。');
},
hobby: { a: '敲代码' }
}
// 1.原型继承:只继承一家, 公有的私有的都继承。私有部分也继承到原型里了。
function Student(sex) {
this.sex = sex;
this.say = function () {
console.log('I am a student.');
}
}
// 这样Student就继承了Person, 公有的私有的都继承了。
Student.prototype = new Person('张三', 18);
// 2.构造函数继承: 能继承多家, 只继承私有部分。
function Monitor(name, age, sex, skill) {
this.skill = skill;
Person.apply(this, [name, age]); // 这样Monitor就继承了Person的私有部分
Student.call(this, sex); // 这样Monitor就继承了Student的私有部分
}
// 3.组合继承(原型链继承 + 构造函数继承)。两次调用父类构造函数,耗性能。
// 4.寄生式继承。只能继承公有部分,只继承一家。
function GroupLeader(group) {
this.group = group;
}
// new创建一个对象,执行构造函数。Object.create也是创建一个对象,但不执行构造函数。
GroupLeader.prototype = Object.create(Person.prototype);
GroupLeader.prototype.constructor = GroupLeader;
// 5.寄生组合式继承(常用),私有和公有都继承了。
function Father(name) {
this.name = name;
this.age = 21;
}
Father.prototype = {
sayName() {
console.log(this.name);
},
address: '上海',
hobby: ['唱歌'],
};
function Sub(name) {
this.aa = 'aa';
Father.call(this, name); // 继承父类构造函数的属性
}
Sub.prototype = Object.create(Father.prototype); // 继承父类的原型
Sub.prototype.constructor = Sub;
25.什么是构造函数?与普通函数有什么区别?
构造函数是一种特殊的函数, 总与new操作符一起使用, 是用来创建对象的。其实构造函数本质上与普通函数并没有区别, 为了区分二者, 人为规定构造函数首字母必须大写, 普通函数首字母则必须小写。构造函数构造出来的对象称为实例对象, 构造函数自带的对象称为原型对象。
26.call和apply 还有bind
共同点:三者都是函数的方法,可以让其它对象调用自己,并把this指向其它对象。箭头函数能使用这三个方法,但箭头函数没有this。
不同点:
apply接收数组参数, 立即执行。
call接收连续参数, 立即执行。
bind接收连续参数, 返回对应函数, 需调用才能执行。
27.js中数组和对象浅拷贝和深拷贝?
浅拷贝: 浅拷贝只是单纯的将引用地址赋值给这个元素
var arr = ["One", "Two", "Three"];
var arrToo = arr; // 单纯的地址赋值, 他们两个指向了同一个地址。
深拷贝: 深拷贝是开辟一块新的内存空间, 深拷贝的引用类型和原来的没有关联了。
// 数组的深拷贝
var arr = ["One", "Two", "Three"];
var arr1 = arr.slice(0);
var arr2 = arr.concat();
// 对象深拷贝
通过JSON方法深拷贝, 存在问题:
1:无法复制函数
2:原型链没了,对象就是object,所属的类没了。
封装方法深拷贝对象
28.实现javascript在String中写一个trim,要求能够去除一个字符串开始和结尾的空格?
function replaceStr(str) {
var reg = /(^\s*)|(\s*$)/g;
return str.replace(reg, "");
}
29.SVG、Canvas 和 WebGL三者之间的区别
SVG是矢量图,是给数据就可以绘制点、线、图形的,基于XML的标记语言;
Canvas是位图,是HTML5新增的一个元素对象,名副其实就是一个画布,配有相应的js操作api,可以不依赖库直接绘图,相当于2D的API。
WebGL是3D位图,是基于Canvas的3D框架。Three.js、Babylon.js、Blender4Web等是几种知名的WebGL开发框架,对WebGL基础操作做了大量的封装,可以拿来就用。
30.Event loop(事件轮询)
js是一门单线程语言,是从上往下执行的。首先执行主线程js代码,主线程上是同步执行的。当主线程上遇到了异步操作,就会把它交给异步线程处理,然后继续执行主线程上的任务。在异步线程里,会先判断异步任务的类型。异步任务可分成宏任务和微任务,像setTimeout、setInterval属于宏任务(js主线程代码也属于宏任务),promise.then属于微任务。不同的任务类型进入不同的队列,等到主线程空闲的时候,就会进入主线程执行。当主线程里的同步任务都执行完后,就会执行主线程里的所有微任务,执行完主线程里的微任务,就执行宏任务队列里的其他宏任务。对于每个宏任务,都是先执行它里面的同步任务,等同步任务都执行完之后,就执行它里面的微任务,等微任务也都执行完了,就开始执行下一个宏任务。按以上步骤重复执行就是事件轮询。
宏任务:Js(主程序代码) setTimeOut setInterVal UI渲染
微任务:promise.then()
执行顺序:
1.先执行第一个宏任务:js主程序代码
2.在上一个宏任务执行完毕之后,如果这个宏任务中有微任务队列,就执行这里的所有微任务队列。Promises.then()
3.执行完第一个宏任务中的微任务,接着执行剩余的宏任务队列。
setTimeout—>setInterval—>UI rendering
注意:new Promise(xx)属于主程序代码, 会立即执行, .then后面的是微任务。
31.为什么JavaScript是单线程?
JS的单线程,这与它的用途有关。作为浏览器脚本语言,JS的主要用途就是与用户进行交互,以及操作DOM。这决定了它只能是单线程,否则会带来非常复杂的同步问题。假如JS同时有两个线程,一个线程要在某个DOM节点上添加内容,另一个线程要删除了这个节点,这时浏览器应该以哪个线程为准?所以,为了避免复杂性,从刚诞生开始,JS就是单线程,这是这门语言的核心特征,将来也不会改变。
为了利用多核CPU的计算能力,HTML5提出Web Workers标准,允许JS创建多个子线程,但是子线程完全受主线程控制,且不得操作DOM。所以这个新标准并没有改变JS单线程的本质。
32.setInterval明明写的间隔300ms,实际却是350ms、400ms秒才执行,这是为什么?
setInterval每次把任务添加到任务队列之前,会先判断上一个任务是否仍在队列中,如果在就等待,如果不在才添加。这就导致setInterval在做定时轮训时,如果调用的异步操作耗时比较多,会导致异步任务不能按照期待的时间间隔来执行。为了解决setInterval的这个缺陷,常用setTimeout封装方法来解决,因为setTimeout不存在等待上一个任务的问题,会直接添加到队列里,setTimeout 保证调用的时间间隔是一致的。
33.为什么for循环比forEach性能高?
for循环没有任何的额外函数调用栈和上下文,而forEach是数组方法的调用,并且传递一个回调函数,在循环的过程中每次都在执行这个回调函数,那么就会有诸多的参数、函数调用栈、执行上下文要考虑进来,这些内容都有可能拖慢forEach的效率,所以for循环的性能要高于forEach。
34.axios和fetch区别对比
axios是基于Promise实现的,对原生ajax的封装。
它支持 Promise API
客户端支持防止CSRF(跨站请求伪造)
提供了一些并发请求的接口(重要,方便了很多的操作)
fetch被称为下一代ajax技术,采用Promise方式处理数据。它的API简洁明了,比XMLHttpRequest更加简单易用,fetch()返回的是一个Promise对象。
fetch存在问题:
fetch不支持abort,不支持超时控制,请求一旦发出就不能撤回,造成了流量的浪费。
fetch没有办法原生监测请求的进度,而XHR可以。
服务器返回400,500错误码时,fetch并不会reject,只有网络错误导致请求不能完成时,fetch才会被reject。
fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: 'include'})
35.堆和栈
栈内存由操作系统自动分配和释放,用于基本数据类型。
堆内存需要通过垃圾回收机制和程序员自己来管理内存。若不主动释放,程序结束时由系统回收,用于存储引用类型。
36.async/await
async函数是Generator函数的语法糖,它是将Generator函数的星号( * )替换成async,将yield替换成await。async函数解决了异步操作深度嵌套的问题。
async与promise对比:
async/await可以用同步的方式写异步代码,Promise是链式调用。
async/await需要通过try/catch来抓取错误,Promise自带catch方法。
async/await可以让try/catch支持抓取异步错误。
多个await之间写条件语句很容易,Promise.then的链式调用较麻烦,上一个then要用分支判断return语句,下一个then还要用分支判断传过来的参数。
async对Generator的改进:
1. 内置执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行,简化了Generator函数处理异步的写法。
2. 更好的语义。async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
3. 返回值是Promise。这比Generator函数的返回值是Iterator对象方便多了。可以用then方法指定下一步的操作。
37.一个网站的js文件过多的情况下,会出现变量名被污染和函数名冲突。有什么解决方法?
1.使用闭包, 一个js文件用一个自执行函数包括着
2.把函数和变量封装到一个对象里
3.规定命名空间
4.使用模块化加载js库
38.script标签的async属性与defer属性
1.浏览器在解析 HTML 的时候,如果遇到一个没有任何属性的script标签 ,就会暂停解析,先发送网络请求获取该 JS 脚本的代码内容,然后让 JS 引擎执行该代码,当js代码执行完毕后恢复HTML解析。
2.给script标签加上defer或async属性,都能异步下载外部的JS脚本,都不会阻塞页面的解析。
3.defer与async的区别:
defer是在JS下载完成后,继续等待所有DOM加载完之后,再去执行js代码
async是在JS下载完成后,立即执行js代码
加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载
39.实现一个 cacheRequest 方法,保证发出多次同一个 ajax 请求时都能拿到的数据,而实际上只发出一次请求
const cache = new Map();
const request = (url, option) => {
return new Promise((res) => {
setTimeout(() => {
res({ data: option });
}, 2000);
});
}
const cacheRequest = (url, option) => {
let key = `${url}:${option.method}`;
if (cache.has(key)) {
if (cache.get(key).status === "pending") {
return cache.get(key).myWait;
}
return Promise.resolve(cache.get(key).data);
} else {
// 无缓存,发起真实请求
const requestApi = request(url, option);
cache.set(key, { status: "pending", myWait: requestApi });
return requestApi
.then((res) => {
cache.set(key, { status: "success", data: res });
Promise.resolve(res);
})
.catch((err) => {
cache.set(key, { status: "fail", data: err });
Promise.reject(err);
});
}
};
40.使用 Symbol 函数都有哪些要注意的点?
1.Symbol 类型的 key 是不能通过 object.keys() 或者 for…in 来枚举的,它未被包含在对象自身的属性名集合之中。所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用 Symbol 来定义。
2.使用 JSON.stringify() 将对象转换成 JSON 字符串的时候,Symbol 属性也会被排除在输出内容之外
获取以 Symbol 方式定义的对象属性:
// 使用Object的API
Object.getOwnPropertySymbol(obj); // [Symbol(name)]// 使用新增的的反射API
Reflect.ownKeys(obj); // [Symbol(name),"age","title"]