一、什么是闭包?闭包的用途是什么?闭包的缺点是什么?

什么是闭包:

简单讲:就是在一个局部作用域中,声明一个函数,这个函数可以访问外部变量。函数和这个外部变量就一起组成了闭包。
  1. function returnFunction() {
  2. let counter = 0
  3. const sum= function() {
  4. counter = counter + 1
  5. return counter
  6. }
  7. return sum
  8. }
  9. const increse = returnFunction()
  10. const result1 = increse()
  11. const result2 = increse()
  12. 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不会立即调用,它会生成一个新的函数,必须调用它才会被执行,否则不会执行。

  1. function add(a,b){
  2. return a+b;
  3. }
  4. add.call(add, 5, 3); //8 连续参数
  5. add.apply(add, [5, 3]); //8 数组参数
  6. var foo = add.bind(add, 5,3);
  7. 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方法

  1. let arr = [1, 5, 2, 3, 4, 2, 3, 1, 3, 4]
  2. const unique = (array) => {
  3. let result = []
  4. for (let i = 0; i < array.length; i++) {
  5. if (result.indexOf(array[i]) === -1) {
  6. result.push(array[i])
  7. }
  8. }
  9. return result
  10. }
  11. console.log(unique(arr))

2.Set方法

  1. let array = [1,5,2,3,4,2,3,1,3,4]
  2. unique = (array) => {
  3. return new Set(array)
  4. }
  5. unique(array)

3.map方法

  1. let array = [1,5,2,3,4,2,3,1,3,4]
  2. unique = (array)=>{
  3. const map = new Map()
  4. let result = []
  5. for (let i = 0; i < array.length; i++) {
  6. if(map.has(array[i])){
  7. continue
  8. }else{
  9. map.set(array[i],true)
  10. result.push(array[i])
  11. }
  12. }
  13. return result
  14. }
  15. unique(array)

五、DOM事件相关:

(1)什么是事件委托?(2)怎么阻止默认动作?(3)怎么阻止事件冒泡?

(1)什么是事件委托(事件代理)?

由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方法叫做事件代理。事件代理的原理是DOM元素的事件冒泡。
简单来说:不监听元素 C 自身,而是监听其祖先元素 P,然后判断 e.target 是不是该元素 C(或该元素的子元素)

(2)怎么阻止默认动作?

什么是默认事件?

如链接的默认动作是跳转到href页面,提交按钮等。当Event 对象的 cancelable为false时,表示没有默认行为,这时即使有默认行为,调用preventDefault也是不会起作用的。

怎么阻止默认事件?

e.preventDefault() 或者 return false

  1. var el = window.document.getElementById("a");
  2. el.onclick =function defaultEvent(e){
  3. if(e && e.preventDefault) { //非IE
  4. e.preventDefault();
  5. } else { //IE(IE11以下)
  6. window.event.returnValue = false;
  7. }
  8. return false;
  9. }

(3)怎么阻止事件冒泡?

w3c使用e.stopPropagation(),IE使用e.cancelBubble = true

  1. function stopHandler(event){
  2. window.event ? window.event.cancelBubble=true : event.stopPropagation();
  3. }
  1. var son = document.querySelector('.son');
  2. son.addEventListener('click', function(e) {
  3. alert('son');
  4. e.stopPropagation();
  5. })
  6. //阻止冒泡,上一个元素的click事件就不会触发了, stop 停止 Propagation 传播

六、如何理解JS的继承?

A对象通过继承B对象,就能直接拥有B对象的所有属性和方法。

1.基于原型链的继承

基本原理是,将父类的实例赋值给子类的原型。
缺点:子类的实例可以访问父类的私有属性,子类的实例还可以更改该属性,这样不安全。
用法:

  1. Child.prototype = new Parent()
  2. Child.prototype.constructor = Child

所有涉及原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向Parent
实例:

  1. function Staff() { //父类
  2. this.company = 'tianchuang';
  3. this.list = [];
  4. }
  5. Staff.prototype.getComName = function() { //父类的原型
  6. return this.company;
  7. };
  8. function Coder(name, skill) { //子类
  9. this.name = name;
  10. this.skill = skill;
  11. }
  12. Coder.prototype = new Staff(); //继承Staff
  13. //因为子类原型的指向已经变了,所以需要把子类原型的contructor指向子类本身
  14. Coder.prototype.constructor = Coder;
  15. Coder.prototype.getInfo = function() { //给子类原型添加属性
  16. return {
  17. name: this.name,
  18. skill: this.skill
  19. };
  20. };
  21. let coder = new Coder('小明', 'javascript');
  22. coder.getInfo(); // {name: '小明', skill: 'javascript'}
  23. coder.getComName(); // 'tianchuang'

2.基于class的继承

ES6中有了类的概念,用class声明一个类,通过extends关键字来实现继承关系。

  1. class A {
  2. constructor(text,color){
  3. this.text = text
  4. this.color = color
  5. }
  6. 普通方法(){}//调用需要实例化这个类,则需要创建对象
  7. //const a = new A()
  8. //a.普通方法()
  9. static 静态方法(){}//直接调用,不能new实例化
  10. console.log(A.静态方法())
  11. }
  12. class B extends A {
  13. constructor() {
  14. super();
  15. }
  16. }

在子类constructor里写this必须在前面写super
静态方法需要被类调用而不能被实例调用

  1. class Polygon {
  2. constructor(height, width) {
  3. this.name = 'Polygon';
  4. this.height = height;
  5. this.width = width;
  6. }
  7. }
  8. class Square extends Polygon {
  9. constructor(length) {
  10. super(height, width);
  11. this.name = 'Square';
  12. }
  13. }

七、数组排序

array = [2,1,5,3,8,4,9,5]
请写出一个函数sort,使得sort(array)得到从小到大排好序的数组[1,2,3,4,5,5,8,9]
新的数组可以是在array自身上改的,也可以是完全新开辟的内存。

使用sort方法或者
最拉胯的冒泡排序,其他的排序方法之后再学

  1. function sort(array) {
  2. for (let i = 0; i < array.length - 1; i++) {
  3. for (let j = 0; j < array.length - i - 1; j++) {
  4. if (array[j] > array[j + 1]) {
  5. let temp = array[j];
  6. array[j] = array[j + 1]
  7. array[j + 1] = temp;
  8. }
  9. }
  10. }
  11. }
  12. let arr = [2, 1, 5, 3, 8, 4, 9, 5]
  13. sort(arr)
  14. console.log(arr);

八、你对 Promise 的了解?

(1)Promise的用途

Promise对象用于表示一个异步操作的最终完成(或失败)及其结果值,是异步编程的一种解决方案。Promise就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法看,Promise 是一个对象,从它可以获取异步操作的消息。比传统的解决方案(回调函数和事件)更合理、强大。

(2)如何创建一个new Promise

  1. return new Promise((resolve,reject)=>{...})

(3)如何使用 Promise.prototype.then

Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为Promise实例添加状态改变时的回调函数。then方法的第1个参数是resolved状态的回调函数,第2个参数(可选)是rejected状态的回调函数。

  1. const promise1 = new Promise((resolve, reject) => {
  2. resolve('Success!');
  3. });
  4. promise1.then((value) => {
  5. console.log(value); // "Success!"
  6. });
  7. /*---------------------------------------*/
  8. var p1 = new Promise((resolve, reject) => {
  9. resolve('成功!');
  10. // reject(new Error("出错了!"));
  11. });
  12. p1.then(value => {
  13. console.log(value); // 成功!
  14. }, reason => {
  15. console.error(reason); // 出错了!
  16. });

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

(4)如何使用Promise.all

Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve)。如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现如下:

  1. var p1 = new Promise(function (resolve, reject) {
  2. setTimeout(resolve, 500, 'P1');
  3. });
  4. var p2 = new Promise(function (resolve, reject) {
  5. setTimeout(resolve, 600, 'P2');
  6. });
  7. // 同时执行p1和p2,并在它们都完成后执行then:
  8. Promise.all([p1, p2]).then(function (results) {
  9. console.log(results); // 获得一个Array: ['P1', 'P2']
  10. });

(5)如何使用Promise.race

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

  1. const promise1 = new Promise((resolve, reject) => {
  2. setTimeout(resolve, 500, 'one');
  3. });
  4. const promise2 = new Promise((resolve, reject) => {
  5. setTimeout(resolve, 100, 'two');
  6. });
  7. Promise.race([promise1, promise2]).then((value) => {
  8. console.log(value); // "two"
  9. // Both resolve, but promise2 is faster
  10. });

九、说说跨域

要点
(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可以访问,具体写法如下:

  1. Access-Control-Allow-Origin: * // 表明该资源可以被任意外域访问。
  2. 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

介绍

动态的展示画一个皮卡丘,其中画的进度可以通过暂停和播放来控制 播放的速度可以通过快速,慢速,中速来控制 最后展示的皮卡丘如果鼠标停在它的嘴上面皮卡丘就可以笑和说话,而且脸的旁边会有“十万伏特”