Kasan Blog

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

function fn(){
    let a = 1;
    function fn2(){
      return  a += 1;
      console.log(a)
    }
    return fn2;
}

let xxx = fn();
xxx();

什么是闭包:

一个函数用到了它外部的变量,那这个函数加那个变量就是闭包。

比如上面代码的fn2的函数用到了它外部fn函数的变量,

那么function fn2( ){ console.log(a) } 和 let a = 1 就是闭包。


闭包的用处:

  1. 可以读取函数内部的变量
  2. 让变量的值始终保持在内存中,不会在被外部函数调用后自动删除

    闭包的缺点:

不合理的使用会造成内存泄漏,由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄漏。解决方法是,在退出函数之前,将不在使用的局部变量全部删除。


call、apply、bind 的用法分别是什么?

call() / apply()

指定函数中的this为第一个参数指定的对象,

如果函数执行需要传参数, call是依次传递, apply需要封装成数组传递

call() 语法: function.call(thisArg, p1, p2, …)

function fn(a,b){
    console.log('name:'+a,'age:'+b)
}

let xxx={'name':'kasan','age':18}

fn.call(xxx, xxx.name, xxx.age) //name:kasan age:18

apply() 语法: function.call(thisArg, [p1, p2, …] )

function fn(a,b){
    console.log('name:'+a,'age:'+b)
}
let xxx={'name':'kasan','age':18}

fn.apply(xxx,[xxx.name,xxx.age]);//name:kasan age:18

bind的用法与call,apply的用法基本一致,区别是bind函数不会像call和apply一样立即调用执行,而是会返回一个新的函数,想执行的时候再执行。

function xxx(a1,b1){
    console.log(a1+b1)
}

let obj = {a:11,b:22}


let xxx1 = xxx.bind(obj,obj.a,obj.b)
xxx1() //33

//如果是call
let xxx2 = xxx.call(obj,obj.a,obj.b)
console.log(xxx2) //undefined

请说出至少 10 个 HTTP 状态码,并描述各状态码的意义。

  1. 200 (OK/正常)
  2. 201 (SC_CREATED)表示服务器在请求的响应中建立了新文档;应在定位头信息中给出它的URL。
  3. 204 (No Content/无内容)
  4. 300 (SC_MULTIPLE_CHOICES)表示被请求的文档可以在多个地方找到,并将在返回的文档中列出来。如果服务器有首选设置,首选项将会被列于定位响应头信息中。
  5.  305 (Use Proxy/使用代理)
  6. 400 (Bad Request/错误请求)
  7. 404 (Not Found/未找到)
  8. 409 (Conflict/冲突)
  9. 410 (Gone/已经不存在)
  10. 414 (Request URI Too Long/请求URI过长)
  11. 504 (Gateway Timeout/网关超时)
  12. 502 (Bad Gateway/错误的网关)

如何实现数组去重? 假设有数组 array = [1,5,2,3,4,2,3,1,3,4] 你要写一个函数 unique,使得 unique(array) 的值为 [1,5,2,3,4] 也就是把重复的值都去掉,只保留不重复的值。

//不用set
array = [1,5,2,3,4,2,3,1,3,4]
function unique(arr){
    let arr1 = [];
    
    for(let i=0;i<arr.length;i++){
        for(let i1=i+1;i1<arr.length;i1++){ 
            let a = arr[i]
            let b = arr[i1]
        if( a===b ){          
            delete arr[i1]
         }
        }
    }
    for(let i2=0;i2<arr.length;i2++){
       if(typeof arr[i2] === 'number'){
        arr1.push(arr[i2])
       }
    }
    return console.log(arr1); 
}
unique(array)
//set()
array = [1,5,2,3,4,2,3,1,3,4]
console.log(new Set(array))
//map 
//主要思路:创建一个空Map,遍历原始数组,把数组的每一个元素作为key存到Map中,因为Map中不会出现相同的key值,所以最终得到的Map中的所有key值就是去重后的结果。
array = [1,5,2,3,4,2,3,1,3,4]
function fn(arr){
    let hashMap = new Map();
    let result = [];
for(let i = 0; i < arr.length; i++ ){
    if(hashMap.has(arr[i])){ //如果hashMap没有arr[i],就添加key为arr[i],值为true表示该值是重复的
        hashMap.set(arr[i],true)
    }else{ //反之为false 表示不重复的值,然后push到result里,返回result。
        hashMap.set(arr[i],false) //
        result.push(arr[i])
    }
  }
  console.log(hashMap) 
  return console.log(result);
}

fn(array)

DOM 事件相关


1.什么是事件委托?

概念: 事件委托也叫事件代理,“事件代理”即是把原本需要绑定在子元素的响应事件(click、keydown…)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。

假设现在要监听100个按钮,效果是:点击不同的按钮时打印出按钮的编号

我们可以通过监听所有button元素的父元素div1,当target为button时,打印出编号

示例


2.怎么阻止默认动作?

有一些html元素默认的行为,比如说a标签,点击后有跳转动作;form表单中的submit类型的input有一个默认提交跳转事件;reset类型的input有重置表单行为。

如果你想阻止这些浏览器默认行为,JavaScript为你提供了方法。 如下代码:

let aaa = document.getElementsByTagName("a")[0];
$aaa.onclick = function(e){
alert("跳转动作被我阻止了")
e.preventDefault();
//默认事件没有了。
}

3.怎么阻止冒泡事件?

使用 e.stopPropagation()

在支持 addEventListener() 的浏览器中,可以调用事件对象的一个 stopPropagation() 方法已阻止事件的继续传播。如果在同一对象上定义了其他处理程序,剩下的处理程序将依旧被调用,但调用 stopPropagation() 方法可以在事件传播期间的任何时间调用,它能工作在捕获阶段、事件目标本身中和冒泡阶段。


你如何理解 JS 的继承?

1.可以只用构造函数,原理是在每个Child1的实例加上Parent1的值和方法,这样做是可以实现继承的功能,但是会大量浪费内存,造成性能下降。

function Parent1(name) {
    this.name = name || '俊宇';
    this.play = [1, 2, 3]
    this.ID = '1';
    this.xxx = function(any){
    console.log(any+',居然可以')
   };
}

function Child1(name,like) {
    Parent1.call(this,name)
    this.like = like ;
    this.doing = function(something){
        console.log(this.name+'在做:'+something+'I Like:'+like)
    }
}

let s1 = new Parent1('P1');
let s2 = new Child1('','apple')
let s3 = new Child1('小明','game')
s2.play.push(4)
console.log(s2.play,s3.play)
s2.ID = '2';
console.log(s2.ID,s3.ID)
s2.xxx('嘻嘻')
s3.xxx('哈哈')
s2.xxx = function(something){console.log(something+',改写了')}
s2.xxx('嘻嘻')
s3.xxx('哈哈')
s2.doing('玩游戏')
s3.doing('打篮球')
console.log(s1.constructor)
console.log(s2.constructor)
console.log(s3.constructor)
console.log(s1)
console.log(s2)
console.log(s3)

所以,需要把复用的方法放在Parent1的prototype上,

然后用create(Parent1.prototype)以Parent1.prototype作为原型构建一个对象返回。

作为中间对象,隔离开子类原型和父类原型。 然后再把constructor指向Child1

就可以比较完美地完成继承。

function Parent1(name) {
    this.name = name || '俊宇';
    this.play = [1, 2, 3]
    this.ID = '1';
}
Parent1.prototype.xxx = function(any){
    console.log(any+',居然可以')
}

function Child1(name,like) {
    Parent1.call(this,name)
    this.like = like ;
    this.doing = function(something){
        console.log(this.name+'在做:'+something+'I Like:'+like)
    }
}

Child1.prototype = Object.create(Parent1.prototype);
Child1.prototype.constructor = Child1;



let s1 = new Parent1('P1');
let s2 = new Child1('','apple')
let s3 = new Child1('小明','game')
s2.play.push(4)
console.log(s2.play,s3.play)
s2.ID = '2';
console.log(s2.ID,s3.ID)
s2.xxx('嘻嘻')
s3.xxx('哈哈')
s2.xxx = function(something){console.log(something+',改写了')}
s2.xxx('嘻嘻')
s3.xxx('哈哈')
s2.doing('玩游戏')
s3.doing('打篮球')
console.log(s1.constructor)
console.log(s2.constructor)
console.log(s3.constructor)
console.log(s1)
console.log(s2)
console.log(s3)


class 继承

extends 关键字

直接子类 extends 父类就可以了,函数和值都可以使用访问。

class Parent{
    constructor(name){
       this.name = name || '俊宇';
       this.play = [1, 2, 3];
       this.ID = '1';
    }
    xxx(any){
        console.log(any+',居然可以')
    }
}


class Child extends Parent{
    constructor(name,like){
        super(name);
        this.like = like;
    }
    doing(something){
        console.log(this.name+'在做:'+something+'I Like:'+this.like)
    }
}

let s1 = new Parent('P1');
let s2 = new Child('','apple')
let s3 = new Child('小明','game')
s2.play.push(4)
console.log(s2.play,s3.play)
s2.ID = '2';
console.log(s2.ID,s3.ID)
s2.xxx('嘻嘻')
s3.xxx('哈哈')
s2.xxx = function(something){console.log(something+',改写了')}
s2.xxx('嘻嘻')
s3.xxx('哈哈')
s2.doing('玩游戏')
s3.doing('打篮球')
console.log(s1.constructor)
console.log(s2.constructor)
console.log(s3.constructor)
console.log(s1)
console.log(s2)
console.log(s3)

给出正整数数组 array = [2,1,5,3,8,4,9,5]

请写出一个函数 sort,使得 sort(array) 得到从小到大排好序的数组 [1,2,3,4,5,5,8,9]

新的数组可以是在 array 自身上改的,也可以是完全新开辟的内存。

array = [2,1,5,3,8,4,9,5]

function sort(arr){
    for(let i = 0; i<arr.length; i++){ 
        for(let k = i+1; k<arr.length; k++){    
            if(arr[i] > arr[k] ){
                let a1 = arr[i]
                let b1 = arr[k]
                arr[i] = b1;
                arr[k] = a1;
            }
        }
    }
     console.log(arr)
}

sort(array)

//Array的sort() ——>console.log(array.sort())

你对 Promise 的了解?

1. Promise 的用途

promise的作用是用来解决异步操作时,成功或失败回调的问题。

如果不用Promise,可能会面临一些问题:

  1. 回调代码命名乱的问题
  2. 可能出现回调地狱
  3. 很难进行错误处理

2.如何创建一个 new Promise

Promise接受两个回调参数第一个参数resolve当异步操作成功时会调用它有一个参数用于传递异步操作成功的结果第二个参数reject当异步操作失败时会调用它有一个参数用于传递异步操作失败的信息

let myPromise = new Promise(function(resolve, reject) {
    ...  //异步操作
    if( success ) {
        resolve(value);
    } else {
        reject(error);
    }
});

如何使用 Promise.prototype.then

then() 方法返回一个 Promise 对象。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。

语法:

p.then((response)=>{}, (request)=>{});

例子:

 let a = 1;
    let promise = new Promise(function (resolve, reject) {
        if (a == 10) {
            resolve('成功')
        } else {
            reject("失败")
        }
     })
    //   promise.then(success,fail)
    promise.then(res => {
        console.log("成功调用", res);
    }, err => {
        console.log("失败调用", err);
    })

如何使用 Promise.all

一个脚本中有多个promise时,监控多个Promise对象

.all([p1,p2,p3,…])

  1. 有一个失败的请求,其他都失败;若需弥补这个缺陷,在每个Promise中增加Catch错误捕捉,且return出来;

  2. 返回结果的顺序按照参数的顺序进行;

语法:

Promise.all(iterable);

例子:

//   promise.all([p1,p2,p3]):把promise打包,扔到一个数组里面,打包完还是一个promise对象
    let p1 = new Promise((resolve, reject) => {
        let time = Math.random() * 4000 + 1000;
        setTimeout(() => {
            console.log('P1完成');
            resolve();
        }, time)
    })
    let p2 = new Promise((resolve, reject) => {
        let time = Math.random() * 4000 + 1000;
        setTimeout(() => {
            console.log('p2完成');
            resolve()
        }, time);
    })
    let p3 = new Promise((resolve, reject) => {
        let time = Math.random() * 4000 + 1000;
        setTimeout(() => {
            console.log('p3完成');
            reject()
        }, time);
    })
    // 必须确保所有Promise 对象都是resolve状态
    let p = Promise.all([p1, p2])
    p.then(() => {
        // p1 和 p2 全部执行完毕后,才会执行then方法里面的操作
        console.log('全部执行完成');
    })

如何使用 Promise.race

假设有三个请求,分别请求三个文件,race有竞速的意思, 所以把返回的第一个响应作为结果,假设第三个请求最先响应,是请求失败的, 那么相当于全局都失败了,就会执行race失败的函数。

再假如第二个请求最先返回,是请求成功的,那么就是全局成功,执行race成功的函数

语法与all一样

例子:

let p1 = new Promise((resolve, reject) => {
    let time = Math.random() * 4000 + 1000;
    setTimeout(() => {
        console.log('P1完成');
        resolve();
    }, time)
})
let p2 = new Promise((resolve, reject) => {
    let time = Math.random() * 4000 + 1000;
    setTimeout(() => {
        console.log('p2完成');
        resolve()
    }, time);
})
let p3 = new Promise((resolve, reject) => {
    let time = 1000 ;
    setTimeout(() => {
        console.log('p3完成');
        reject()
    }, time);
})
// 谁先执行完成,race就是谁的状态,去执行then,比如上面第一个执行完成的是
//p3,那么promise就是rejected状态,触发then的reject函数
let p = Promise.race([p1, p2,p3])
p.then(() => {
    // 
    console.log('全部执行完成');
},()=>{console.log('未执行完成');})


跨域


什么是同源

定义:地址不完全相同的网站不能获取对方的数据内容

哪怕是域名,ip,端口,等等有不一样的地方,都不能获取对方不想被获取的数据

因为历史原因,当年的服务器和带宽都十分昂贵,一个服务器可以跑很多网站,所以浏览器需要定制一个策略来限制不同网站间的数据获取,

否则所有网站的数据都会被泄露,浏览器就变成一个纯纯的病毒。

数据可以被引用,但不能被获取

比如一个网站A的js文件,它可以被网站B下载,运行,但是如果网站A不想共享该文件,浏览器就不会把源码展示出来,这是浏览器为数据安全作出的隔离策略,叫同源策略。


什么是跨域

简单来说,跨域就是为了突破浏览器的同源策略,实现主动共享数据的需求。


CORS 跨域

只需要在后台把需要共享的数据设置头为对方地址,对方就可以获取到这个数据。

比如我想共享数据给ip为127.0.0.1,端口为8889的网站好友的信息,那么我只需要在后台设置一个头,输入对方的地址就可以了,数据就定向共享出去了。

response.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:8889')
else if(path === '/friends.json'){
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/json;charset=utf-8')
    response.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:8889') //localhost === 127.0.0.1
    response.setHeader('Access-Control-Allow-Origin','http://localhost:8889')
    response.write(fs.readFileSync('./public/friends.json'))
    response.end()
}

在接收数据的网站写:AJAX请求对方的数据地址,然后打印出获取的数据

const request = new XMLHttpRequest()
request.open('GET', 'http://127.0.0.1:8888/friends.json')
request.onreadystatechange = ()=>{
  if(request.readyState===4 && request.status === 200){
    console.log(request.response)
  }
}
request.send()

JSONP 跨域

很不幸的是,IE 6,7,8,9并不支持CORS跨域

所以如果需要为IE做跨域,只能另辟蹊径,前端工程师想出了一种方法,把数据打包在js里,用js发送这些数据出去。

你需要先创建一个用来装数据分享出去的js文件

然后把需要共享的数据替换到js里,然后响应发送出去。

else if(path === '/friends.js'){
    response.statusCode = 200
    response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
    const string1 = fs.readFileSync('./public/friends.js').toString();
    const data = fs.readFileSync('./public/friends.json').toString();
    const string2 = string1.replace('',data)
    response.write(string2)
    response.end()
}

在接收数据的网站,创建一个script标签,把对方共享的数据地址写到script标签

然后把script标签加到body,打印出来,就获取到对方要共享的数据了

const script = document.createElement('script')
script.src = 'http://127.0.0.1:8888/friends.js'
script.onload = () => {
    console.log(window.xxx)
}
document.body.appendChild(script)

到这里就完成了JSONP的基本应用

但是现在还有些问题,比如这样写的话,所有网站都可以获取到你要定向共享的数据

这样就没有意义了,所以我们需要定向共享的话,就需要做一个 referer 检查

假如访问方的地址不为:http://127.0.0.1:8889 ,就返回404。

如果是 http://127.0.0.1:8889 ,就返回共享的数据

这样可以做到定向访问数据,但是如果对方网站被攻陷,那么我们的共享数据也会被偷走

安全的上限是取决于该安全链条上最薄弱的一环。

所以后面需要添加更多的验证来提高安全水平。

if(request.headers['referer'].indexOf('http://127.0.0.1:8889')){ 
        const string1 = fs.readFileSync('./public/friends.js').toString();
        const data = fs.readFileSync('./public/friends.json').toString();
        const string2 = string1.replace('',data)
        response.write(string2)
        response.end()
    }else{
        response.statusCode = 404;
        response.end();
    }
    

对前端的理解

1990 年,第一个web浏览器诞生,Tim 以超文本语言 HTML 为基础在 NeXT 电脑上发明了最原始的 Web 浏览器。
1991 年,WWW诞生,这标志着前端技术的开始。
在那个前后端不分的年代,网站采用静态网页为人们传递信息,
画面简陋,观感差,优化用户看到的画面成为了一种隐性需求。

后来,
1994年哈肯·维姆·莱提出了CSS的最初建议。伯特·波斯(Bert Bos)当时正在设计一个叫做“Argo”的浏览器,他们决定一起合作设计CSS。
CSS就这样开始诞生,虽然一开始的CSS只支持一些字体的大小,字形,强调,颜色,字的距离等等这种简单的功能,但是在那个年代,我敢肯定是网页观感的巨大提升。

再后来,网页需要交互,JS诞生了,前后端需要分离,AJAX诞生了,
一个一个新颖的技术如雨后春笋般冒出。

到2022年,经过数十年的发展,前端工程师早成为程序开发的一条独立分支,
前端工程师,别称web前端开发攻城狮,是在2005年由淘宝发明出来的称呼。前端工程师通过前端技术完成界面设计,界面制作,用户交互,网站维护、网站优化等等。
通俗点讲,可以设计、制作网页,给网页加上各种各样的特效和功能。

前端需要学会哪些技术?
基础:html+css+javascript,html5+css3
进阶:各种框架,如Bootstrap,jquery,react,vue,angular等等
其他:http,一门后端语言,网站优化等等

我对前端的理解:在网络平台上,展示、传递信息给用户,或跟用户进行交流的界面,优化画面,在观感上给用户带来极高的体验。