一、什么是闭包?闭包的用途是什么?闭包的缺点是什么?
什么是闭包:
简单讲:就是在一个局部作用域中,声明一个函数,这个函数可以访问外部变量。函数和这个外部变量就一起组成了闭包。
function returnFunction() {
let counter = 0
const sum= function() {
counter = counter + 1
return counter
}
return sum
}
const increse = returnFunction()
const result1 = increse()
const result2 = increse()
console.log(result1, result2)//结果输出,1,2
首先声明一个returnFunction函数,创建了一个局部作用域
在该作用域中声明一个counter变量,赋值为0,在声明一个sum变量,赋值为函数的引用
在sum函数中,对counter进行了+1操作,这时sum函数用到了自身以外另一个函数中的变量,这时sum函数和这个counter变量形成了闭包
函数执行结束,返回sum函数的引用
注意:这个时候不仅返回了sum函数的引用,还带着counter的值一起返回了,这时counter的值为1
接着result2=increse(),这时increse包含一个函数的引用,和一个闭包变量counter,值为1。所以result2的结果为2
闭包的优点:
可以避免全局变量的污染,隐藏局部变量
闭包的缺点:
外层函数调用后,外层函数的函数作用域对象无法释放,被内层函数引用着,无法自动释放内存。
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
二、call、apply、bind的用法分别是什么?
call是函数的正常调用方式,可指定上下文this。
apply作用和call一样,只是在调用的时传参数的方式不同。区别是 apply接受的是数组参数,call接受的是连续参数。
bind接受的参数跟call一致,都接受数组参数。只是bind不会立即调用,它会生成一个新的函数,必须调用它才会被执行,否则不会执行。
function add(a,b){
return a+b;
}
add.call(add, 5, 3); //8 连续参数
add.apply(add, [5, 3]); //8 数组参数
var foo = add.bind(add, 5,3);
foo(); //8 调用才会执行
三、常见HTTP状态码
1xx: 表示目前是协议处理的中间状态,还需要后续操作。
101 Switching Protocols。在HTTP升级为WebSocket的时候,如果服务器同意变更,就会发送状态码 101。
2xx: 表示成功状态。
200 OK是见得最多的成功状态码。通常在响应体中放有数据。
204 No Content含义与 200 相同,但响应头后没有 body 数据。
3xx: 重定向状态,资源位置发生变动,需要重新请求。
301 Moved Permanently即永久重定向,对应着302 Found,即临时重定向。
4xx: 请求报文有误。
404 Not Found: 资源未找到,表示没在服务器上找到相应的资源。
405 Method Not Allowed: 请求方法不被服务器端允许。
406 Not Acceptable: 资源无法满足客户端的条件。
408 Request Timeout: 服务器等待了太长时间。
409 Conflict: 多个请求发生了冲突。
414 Request-URI Too Long: 请求行里的 URI 太大。
429 Too Many Request: 客户端发送的请求过多。
431 Request Header Fields Too Large请求头的字段内容太大。
5xx: 服务器端发生错误。
501 Not Implemented: 表示客户端请求的功能还不支持。
503 Service Unavailable: 表示服务器当前很忙,暂时无法响应服务。
四、如何实现数组去重?
假设有数组 array = [1,5,2,3,4,2,3,1,3,4]
你要写一个函数unique,使得unique(array) 的值为 [1,5,2,3,4]
1.index方法
let arr = [1, 5, 2, 3, 4, 2, 3, 1, 3, 4]
const unique = (array) => {
let result = []
for (let i = 0; i < array.length; i++) {
if (result.indexOf(array[i]) === -1) {
result.push(array[i])
}
}
return result
}
console.log(unique(arr))
2.Set方法
let array = [1,5,2,3,4,2,3,1,3,4]
unique = (array) => {
return new Set(array)
}
unique(array)
3.map方法
let array = [1,5,2,3,4,2,3,1,3,4]
unique = (array)=>{
const map = new Map()
let result = []
for (let i = 0; i < array.length; i++) {
if(map.has(array[i])){
continue
}else{
map.set(array[i],true)
result.push(array[i])
}
}
return result
}
unique(array)
五、DOM事件相关:
(1)什么是事件委托?(2)怎么阻止默认动作?(3)怎么阻止事件冒泡?
(1)什么是事件委托(事件代理)?
由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方法叫做事件代理。事件代理的原理是DOM元素的事件冒泡。
简单来说:不监听元素 C 自身,而是监听其祖先元素 P,然后判断 e.target 是不是该元素 C(或该元素的子元素)
(2)怎么阻止默认动作?
什么是默认事件?
如链接的默认动作是跳转到href页面,提交按钮等。当Event 对象的 cancelable为false时,表示没有默认行为,这时即使有默认行为,调用preventDefault也是不会起作用的。
怎么阻止默认事件?
e.preventDefault() 或者 return false
var el = window.document.getElementById("a");
el.onclick =function defaultEvent(e){
if(e && e.preventDefault) { //非IE
e.preventDefault();
} else { //IE(IE11以下)
window.event.returnValue = false;
}
return false;
}
(3)怎么阻止事件冒泡?
w3c使用e.stopPropagation(),IE使用e.cancelBubble = true
function stopHandler(event){
window.event ? window.event.cancelBubble=true : event.stopPropagation();
}
var son = document.querySelector('.son');
son.addEventListener('click', function(e) {
alert('son');
e.stopPropagation();
})
//阻止冒泡,上一个元素的click事件就不会触发了, stop 停止 Propagation 传播
六、如何理解JS的继承?
1.基于原型链的继承
基本原理是,将父类的实例赋值给子类的原型。
缺点:子类的实例可以访问父类的私有属性,子类的实例还可以更改该属性,这样不安全。
用法:
Child.prototype = new Parent()
Child.prototype.constructor = Child
所有涉及原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向Parent
实例:
function Staff() { //父类
this.company = 'tianchuang';
this.list = [];
}
Staff.prototype.getComName = function() { //父类的原型
return this.company;
};
function Coder(name, skill) { //子类
this.name = name;
this.skill = skill;
}
Coder.prototype = new Staff(); //继承Staff
//因为子类原型的指向已经变了,所以需要把子类原型的contructor指向子类本身
Coder.prototype.constructor = Coder;
Coder.prototype.getInfo = function() { //给子类原型添加属性
return {
name: this.name,
skill: this.skill
};
};
let coder = new Coder('小明', 'javascript');
coder.getInfo(); // {name: '小明', skill: 'javascript'}
coder.getComName(); // 'tianchuang'
2.基于class的继承
ES6中有了类的概念,用class声明一个类,通过extends关键字来实现继承关系。
class A {
constructor(text,color){
this.text = text
this.color = color
}
普通方法(){}//调用需要实例化这个类,则需要创建对象
//const a = new A()
//a.普通方法()
static 静态方法(){}//直接调用,不能new实例化
console.log(A.静态方法())
}
class B extends A {
constructor() {
super();
}
}
在子类constructor里写this必须在前面写super
静态方法需要被类调用而不能被实例调用
class Polygon {
constructor(height, width) {
this.name = 'Polygon';
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(length) {
super(height, width);
this.name = 'Square';
}
}
七、数组排序
array = [2,1,5,3,8,4,9,5]
请写出一个函数sort,使得sort(array)得到从小到大排好序的数组[1,2,3,4,5,5,8,9]
新的数组可以是在array自身上改的,也可以是完全新开辟的内存。
使用sort方法或者
最拉胯的冒泡排序,其他的排序方法之后再学
function sort(array) {
for (let i = 0; i < array.length - 1; i++) {
for (let j = 0; j < array.length - i - 1; j++) {
if (array[j] > array[j + 1]) {
let temp = array[j];
array[j] = array[j + 1]
array[j + 1] = temp;
}
}
}
}
let arr = [2, 1, 5, 3, 8, 4, 9, 5]
sort(arr)
console.log(arr);
八、你对 Promise 的了解?
(1)Promise的用途
Promise对象用于表示一个异步操作的最终完成(或失败)及其结果值,是异步编程的一种解决方案。Promise就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法看,Promise 是一个对象,从它可以获取异步操作的消息。比传统的解决方案(回调函数和事件)更合理、强大。
(2)如何创建一个new Promise
return new Promise((resolve,reject)=>{...})
(3)如何使用 Promise.prototype.then
Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调函数。then方法的第1个参数是resolved状态的回调函数,第2个参数(可选)是rejected状态的回调函数。
const promise1 = new Promise((resolve, reject) => {
resolve('Success!');
});
promise1.then((value) => {
console.log(value); // "Success!"
});
/*---------------------------------------*/
var p1 = new Promise((resolve, reject) => {
resolve('成功!');
// reject(new Error("出错了!"));
});
p1.then(value => {
console.log(value); // 成功!
}, reason => {
console.error(reason); // 出错了!
});
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
(4)如何使用Promise.all
Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve)。如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。
一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现如下:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 获得一个Array: ['P1', 'P2']
});
(5)如何使用Promise.race
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value); // "two"
// Both resolve, but promise2 is faster
});
九、说说跨域
要点
(1)什么是同源
(2)什么是跨域
(3)JSONP跨域
(4)CORS跨域
(1)什么是同源
源=协议+域名+端口号。
如果两个url的协议、域名、端口号完全一致,那么这两个url就是同源的。同源策略即:不同源之间的页面,不准互相访问数据。例如js运行在源A里,那么就只能获取源A的数据,不允许获取源B的数据,即不允许跨域。可以通过window.origin或location.origin得到当前源。
(2)什么是跨域
不同源页面之间的访问,浏览器试图执行其他网站的脚本。但是由于同源策略的限制,导致我们无法实现跨域。
(3)CORS跨域
CORS的全称是”跨域资源共享”。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS是HTTP的一部分,它允许服务端来指定哪些主机可以从这个服务端加载资源。
例如,源B要访问源A,源A就要在响应头里声明源B可以访问,具体写法如下:
Access-Control-Allow-Origin: * // 表明该资源可以被任意外域访问。
Access-Control-Allow-Origin: http://foo.example //限制访问,只能http://foo.example访问
(4)JSONP跨域
跨域时由于某些原因不支持CORS,这时可以使用JSONP请求一个JS文件,这个JS文件执行一个回调,回调里面就是我们需要的数据。过程如下:
- 利用script标签可以跨域访问资源的特点
- 目标网站将数据写到js文件中
- 发送请求的网站用script标签引入目标网站写好的js文件
- 一旦引入成功,发送请求的网站就会执行目标网站写好的js文件
- 这样发送请求的网站就拿到了目标网站的数据了
优点:兼容IE,可以跨域
缺点:因为是script标签,所以拿不到状态码也拿不到header,不支持POST只支持GET
介绍
动态的展示画一个皮卡丘,其中画的进度可以通过暂停和播放来控制 播放的速度可以通过快速,慢速,中速来控制 最后展示的皮卡丘如果鼠标停在它的嘴上面皮卡丘就可以笑和说话,而且脸的旁边会有“十万伏特”