初级-JS基础

222 阅读15分钟

适合初中级前端面试(0~2年)梳理JS知识体系

image.png

一、变量类型和计算

1、typeof 能判断哪些类型

值类型
//常见值类型
let a //undefined
const s = 'abc' //string
const n = 100 //number
const b = true //boolean
const s = symbol('s') //symbol

//值类型
let a = 100
let b = a
a = 200
console.log(b) //100
 引用类型
//常见引用类型
const obj = { x: 100 }
const arr = ['a', 'b', 'c']
const n = null //特殊引用类型,指针指向空地址
//特殊引用类型,但不用于存储数据,没有"拷贝"、"复制函数"这一说
function fn(){}

//引用类型
let a = { age: 20 }
let b = a
b.age = 21
console.log(a.age) //21

typeof运算符

  • 识别所有值类型(undefined、string、number、boolean、symbol)
  • 识别函数 function
console.log(typeof console.log) //function 
console.log(typeof function(){}) //function
  • 判断是否是引用类型(不可再细分
console.log(typeof null) //object 
console.log(typeof {x:100}) //object

2、何时使用 === 何时使用 ==

image.png

3、值类型和引用类型的区别

值类型 vs 引用类型 (堆栈模型)

// 值类型
var a=10
var b=a
a=11
console.log(b) //10


// 引用类型
var obj1={x:100}
var obj2=obj1
obj1.x=200
console.log(obj2.x) // 200

值类型和引用类型的区别
const obj1={x:100,y:200}
const obj2=obj1
let x1=obj1.x
obj2.x=101
x1=102
console.log(obj1)// {x:101}

4、手写深拷贝

  • 注意判断值类型和引用类型
  • 注意判断数组还是对象
  • 递归
/**
 * 深拷贝
 * @param {Object} obj 要拷贝的对象
 */
function deepClone(obj = {}) {
    if (typeof obj !== 'object' || obj == null) {
        // obj 是 null ,或者不是对象和数组,直接返回
        return obj
    }

    // 初始化返回结果
    let result
    if (obj instanceof Array) {
        result = []
    } else {
        result = {}
    }

    for (let key in obj) {
        // 保证 key 不是原型的属性
        if (obj.hasOwnProperty(key)) {
            // 递归调用!!!
            result[key] = deepClone(obj[key])
        }
    }

    // 返回结果
    return result
}
const obj1 = {
    age: 20,
    name: 'xxx',
    address: {
        city: 'beijing'
    },
    arr: ['a', 'b', 'c']
}

const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])

知识点:

变量计算——类型转换

  1. 字符串拼接
console.log(100 + '20') //10020
100 + 10 // 110
true + '10' // 'true10'

100=='100' // true
0=='' // true
0==false // true
false == '' // true
null == undefined // true

任何类型内容+字符串类型内容相当于字符串拼接,注意转换类型

  1. ==
100 == '100'  //true
0 == '' //true
0 == false //true
false == '' //true
null == undefined //true
//除了==null之外,其他一律用===,例如
const obj = { x: 100 }
if(obj.a == null){ }
//相当于if(obj.a === null || obj.a === undefined){}

  1. if语句和逻辑运算

    if语句
    image.png

  • truly变量:!!a === true的变量
    if语句中只有truly变量才会执行
  • falsely变量:!!a === false的变量
    除了!!0、!!NaN、!!’’、!!null、!!undefined、!!false,其他都是truly变量

逻辑判断

  • &&
  • ||
  • !
console.log(10 && 0) //0
console.log(0 && 10) //0
console.log('' || 'abc') //'abc'
console.log('abc' || '') //'abc'
console.log(!window.abc) //true

额外

说一下对变量提升的理解

console.log(a) 
var a=100 

fn('zhangsan') 
function fn(name){ 
    age=20; 
    console.log(name,age) 
    var age; 
    
    bar(100) 
    function bar(num){ 
        console.log(num) 
        } 
     } 
     
     
// 输出 
// zhangsan 20 
// 100

输出结果-1.png this指的是window

arguments指的是 这个集合体

二、原型和原型链

1、如何准确判断一个变量是不是数组

a instanceof Array

Array.isArray([]);

Object.prototype.toString.apply(b);//"[object Object]"

....

2、手写一个简易的 jQuery ,考虑插件和扩展性

class jQuery {
    constructor(selector) {
        const result = document.querySelectorAll(selector)
        const length = result.length
        for (let i = 0; i < length; i++) {
            this[i] = result[i]
        }
        this.length = length
        this.selector = selector
    }
    get(index) {
        return this[index]
    }
    each(fn) {
        for (let i = 0; i < this.length; i++) {
            const elem = this[i]
            fn(elem)
        }
    }
    on(type, fn) {
        return this.each(elem => {
            elem.addEventListener(type, fn, false)
        })
    }
    // 扩展很多 DOM API
}

// 插件
jQuery.prototype.dialog = function (info) {
    alert(info)
}

// “造轮子”
class myJQuery extends jQuery {
    constructor(selector) {
        super(selector)
    }
    // 扩展自己的方法
    addClass(className) {

    }
    style(data) {
        
    }
}

// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))

3、class的原型本质,怎么理解

  • 原型和原型链的图示
  • 属性和方法的执行规则

image.png

知识点:

class

class是一个类,相当于模板,可以new一个类得到对象/实例

包含constructor、属性、方法

// 类
class Student {
    constructor(name, number) {
        this.name = name
        this.number = number
    }
    sayHi() {
        console.log(
            `姓名 ${this.name} ,学号 ${this.number}`
        )
    }
}
// class是一个类,相当于模板,可以new一个类得到对象/实例
// 通过类 new 对象/实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()

const madongmei = new Student('马冬梅', 101)
console.log(madongmei.name)
console.log(madongmei.number)
madongmei.sayHi()

继承

  • extends
  • super:执行父类的构造函数、构建过程
  • 扩展或重写方法
// 父类
class People {
    constructor(name) {
        this.name = name
    }
    eat() {
        console.log(`${this.name} eat something`)
    }
}

// 子类
class Student extends People {
    constructor(name, number) {
        super(name)
        this.number = number
    }
    sayHi() {
        console.log(`姓名 ${this.name} 学号 ${this.number}`)
    }
}

// 子类
class Teacher extends People {
    constructor(name, major) {
        super(name)
        this.major = major
    }
    teach() {
        console.log(`${this.name} 教授 ${this.major}`)
    }
}

// 实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()
xialuo.eat()

// 实例
const wanglaoshi = new Teacher('王老师', '语文')
console.log(wanglaoshi.name)
console.log(wanglaoshi.major)
wanglaoshi.teach()
wanglaoshi.eat()

扩展或重写方法

//手写简易jQuery考虑插件和扩展性
class jQuery {
    constructor(selector) {
        const result = document.querySelectorAll(selector)
        const length = result.length
        for (let i = 0; i < length; i++) {
            this[i] = result[i]
        }
        this.length = length
        this.selector = selector
    }
    get(index) {
        return this[index]
    }
    each(fn) {
        for (let i = 0; i < this.length; i++) {
            const elem = this[i]
            fn(elem)
        }
    }
    on(type, fn) {
        return this.each(elem => {
            elem.addEventListener(type, fn, false)
        })
    }
    // 扩展很多 DOM API
}

// 插件
jQuery.prototype.dialog = function (info) {
    alert(info)
}

// “造轮子”
class myJQuery extends jQuery {
    constructor(selector) {
        super(selector)
    }
    // 扩展自己的方法
    addClass(className) {

    }
    style(data) {
        
    }
}

// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))

类型判断(instanceof)

判断变量属于哪个class,属于哪个构造函数

xialuo instanceof Student //true
xialuo instanceof People //true
xialuo instanceof Object //true

xialuo instanceof Array //false
[] instanceof Array //true
[] instanceof Object //true
{} instanceof Object //true

原型

  • typeof People === ‘function’ //class实际上是函数,可见是语法糖
  • hasOwnProperty 判断是不是自己的属性
xialuo.hasOwnProperty('name')  //true
xialuo.hasOwnProperty('saiHi')  //false
xialuo.hasOwnProperty('hasOwnProperty')  //false

  • 隐式原型(proto ),显式原型(prototype)

image.png

image.png

原型关系:

  • 每个 class 都有显示原型 prototype
  • 每个实例都有隐式原型 proto
  • 实例的__proto__指向对应 class 的 prototype

基于原型的执行规则:

  • 获取属性 xiaoluo.name或执行方法 xialuo.sayHo()时
  • 先在自身属性和方法寻找
  • 如果找不到则自动去__proto__ 中查找

原型链

原型链.png

People.prototype === Student.prototype.__proto__

instanceof判断技巧:顺着变量的隐式原型一直往上找,看能不能对应到class的显式原型,能instanceof成立,不能返回false

额外

描述new一个对象的过程

// 构造函数
function Foo(name,age) {   
    this.name=name;     
    this.age=age;     
    this.class='class-1';     
    // return this  // 默认有一行 
    } 
 var f=new Foo('zhangsan',20); 
 // var f1=new Foo('list',21)  // 可以创建多个对象console.log(f);

构造函数—扩展

Var a={} 其实是var a=new Object()的语法糖

Var a=[] 其实是var a=new Array()的语法糖

Function Foo(){…} 其实是var Foo =new Function(…)

所有的引用类型(数组,对象,函数),都具有对象特性,即可自由扩展属性(除了”null”以外)

原型规则和示例.png 所有的引用类型(数组、对象、函数),都有一个__proto__属性,属性值是一个普通的对象

隐式原型

所有的函数,都有一个prototype属性,属性值也是一个普通的对象

显式原型

所有的引用类型(数组、对象、函数),__proto__属性(隐式原型)值指向它的构造函数的”prototype”属性值(显式原型)

原型规则和示例-2.png 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找

console.log(obj.__proto__)===Object.prototype

原型规则和示例-3.png Zhangsan

弹出  Zhangsan

循环对象自身的属性

循环对象自身的属性
var item
for(item in f){
 //  高级浏览器已经在for in中屏蔽了来自原型的属性
 // 但是这里建议大家还是加上这个判断,保证程序的健壮性
 if(f.hasOwnproperty(item)){
    console.log(item)
    }
}

原型链.png

原型链图.png

new一个对象的过程.png

写一个封装DOM查询的例子

面试时候问原型链实现的方法和方式(代码实现)实战中原型是怎么用的:

1.png

2.png

type:是click还是别的事件     fn:元素

写一个原型链继承的例子   这个写不出来面试肯定过不了 原型链继承-0.png hashiqi 本身的prototype就有bark属性,在它的隐士原型 new Animal(),有eat属性,所以有两个属性

三、作用域和闭包

1、this 的不同应用场景,如何取值?(函数执行的时候确定,定义的时候不能确定)

  • 当作普通函数被调用(返回Window)
  • 使用 call apply bind (传入什么绑定什么)
  • 作为对象方法调用 (返回对象本身)
  • 在class 的方法中调用 (当前实例本身)
  • 箭头函数 (找上级作用域中this的值)

2、手写 bind 函数

手写bind函数.png

// 模拟 bind
Function.prototype.bind1 = function () {
    // 将参数拆解为数组
    const args = Array.prototype.slice.call(arguments)

    // 获取 this(数组第一项)
    const t = args.shift()

    // fn1.bind(...) 中的 fn1
    const self = this

    // 返回一个函数
    return function () {
        return self.apply(t, args)
    }
}

function fn1(a, b, c) {
    console.log('this', this)
    console.log(a, b, c)
    return 'this is fn1'
}

const fn2 = fn1.bind1({x: 100}, 10, 20, 30)
const res = fn2()
console.log(res)


3、实际开发中闭包的应用场景,举例说明

  • 隐藏数据
  • 如做一个简单的 cache工具

应用场景:实现一个简单的cache工具

// 闭包隐藏数据,只提供 API
function createCache() {
    const data = {} // 闭包中的数据,被隐藏,不被外界访问
    return {
        set: function (key, val) {
            data[key] = val
        },
        get: function (key) {
            return data[key]
        }
    }
}

const c = createCache()
c.set('a', 100)
console.log( c.get('a') )


4、创建10个a标签,点击的时候弹出对应序号

/*创建10个<a>标签 点击时候弹出来对应的序号*/
/*这是一个错误的写法*/
var i,a
for(i=0;i<10;i++){
    a=document.createElement('a')
    a.innerHTML=i+'<br>'
    a.addEventListener('click',function (e) {
    e.preventDefault()
    alert(i)
})
document.body.appendChild(a)
}

原因:i 为全局变量,click 点击时间只有点击才触发函数,此时for循环早已结束,i = 10, 所以点击任何一个都弹出 10.

/*创建10个<a>标签 点击时候弹出来对应的序号*/
/*这是正确的写法*/
var i
for(var i=0;i<10;i++){
   (function (i) {
       var a=document.createElement('a')
       a.innerHTML=i+'<br>'
       a.addEventListener('click',function (e) {
           e.preventDefault()
           alert(i)
       })
   document.body.appendChild(a)
})(i)
}

或者

// let i 是块级作用域
let a
for(let i=0;i<10;i++){
    a=document.createElement('a')
    a.innerHTML=i+'<br>'
    a.addEventListener('click',function (e) {
        e.preventDefault()
        alert(i)
    })
document.body.appendChild(a)
}

此时 i 定义为for中块级作用域,每次for 循环都会形成一个块级作用域,每次点击在相应的块级作用域中找值。

知识点:

作用域

作用域指变量的合法的使用范围,包含

  • 全局作用域
  • 函数作用域
  • 块级作用域( ES6 新增)
// es6块级作用域
if(true){
 let x=100
}
console.log(x)// 会报错

image.png

自由变量

自由变量:当前作用域没有定义的变量

  • 一个变量在当前作用域没有定义,但被使用了
  • 向上级作用域,一层一层依次寻找,直至找到为止
  • 如果到全局作用域都没有找到,则报错 xx is not defined

闭包

作用域应用的特殊情况,有两种表现:

  • 函数作为参数被传递
  • 函数作为返回值被返回
// 函数作为返回值
 function create() {
     const a = 100
     //返回一个函数(函数作为返回值)
     return function () {
         console.log(a)/*自由变量,父作用域寻找100*/
     }
 }

// const fn = create()
// const a = 200
// fn() // 100

// 函数作为参数被传递
function print(fn) {
    const a = 200
    fn()
}
const a = 100
function fn() {
    console.log(a)
}
print(fn) // 100

// 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找 // 不是在执行的地方!!!

自由变量查找规则:所有的自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方!!!

额外

//    /*实际开发中闭包的应用*/     
//闭包实际应用中主要用于封装变量、收敛权限     
function isFirstLoad() {         
    var _list=[]         
    return function (id) {             
        if(_list.indexOf(id)>=0){                 
            return false             
         }else{                 
             _list.push(id)                 
             return true             
             }         
         }     
}     
//使用     
var firstLoad=isFirstLoad()     
firstLoad(10)//true     
firstLoad(10)//false     
firstLoad(20)//true     
firstLoad(20)//false     
//你在isfirstLoad函数外面,根本不可能修改掉_list的值

// 作用域链 
var a=100 
function F1(){ 
    var b=200 
    function F2(){ 
        var c=300 
        console.log(a) 
        console.log(b) 
        console.lg(c) 
     } 
    F2() 
} 
F1() 
// 作用链就是一个作用域  一直往上找。 
// 输出: 
// 100 
// 200 
// 300
function Foo(name){ 
    this.name=name 
} 

var f=new Foo('zhangsan') 
var obj={ 
    name:'A', 
    printName:function(){ 
        console.log(this.name) 
     } 
obj.printName() 
function fn(){ 
    console.log(this) 
   } 
fn() 
//obj.printName()指向obj 
// fn() 指向window 

// call apply bind 

function fn1(name,age){ 
    alert(name) 
    console.log(this) 
    } 
fn1.call({x:100},'zhangsan',20) 
// 弹出zhangsan,this是{x:100} 

var fn2=function(name,age){ 
    alert(name) 
    console.log(this) 
 }.bind({y:200}) 
 fn2('zhangsan'.20) 
 // 弹出 zhangsan  this是Object{y:200}
// 无块级作用域 
if(true){ 
    var name='zhangsan' 
} 
console.log(name) 
// 第5行 输出 zhangsan   。不建议将声明写在if判断里。写在里面和写在外边是一样的。 

// es6块级作用域 
if(true){ let x=100 } 
console.log(x)
// 会报错 

// 函数和全局作用域 
var a=100 
function fn(){ 
    var a=200 
    console.log('fn',a) 
 } 
 console.log('global',a) 
 fn() 
 // fn 200 
 // global 100

this

场景:

  • 作为普通函数去调用时值为window
  • 使用call bind apply去调用,传入什么值为什么
  • 作为对象方法被调用,返回对象本身
  • 在class方法中调用,创建实例的本身
  • 箭头函数:this永远取它上级作用域的this,它自己本身不会决定this值

this要在执行时才能确认值,定义时无法确认

// this 
function fn1(){ 
    console.log(this) 
    } 
fn1()// window 

fn1.call({x:100})// {x:100} 

const fn2=fn1.bind({x:200}) 
fn2()// {x:200}
var a={ 
    name:'A', 
    fn:function(){ 
        console.log(this.name) 
        } 
    } 
a.fn() // this===a 
a.fn.call({name:'B'}) // this==={name:'B'} 
var fn1=a.fn 
fn1() // this===window

this-2.png

this-1.png

四、异步和单线程

1、手写用Promise 加载一张图片

function loadImg(src) {
    const p = new Promise(
        (resolve, reject) => {
            const img = document.createElement('img')
            img.onload = () => {
                resolve(img)
            }
            img.onerror = () => {
                const err = new Error(`图片加载失败 ${src}`)
                reject(err)
            }
            img.src = src
        }
    )
    return p
}

// const url = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
// loadImg(url).then(img => {
//     console.log(img.width)
//     return img
// }).then(img => {
//     console.log(img.height)
// }).catch(ex => console.error(ex))

// 2张图片
const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'

loadImg(url1).then(img1 => {
    console.log(img1.width)
    return img1 // 普通对象
}).then(img1 => {
    console.log(img1.height)
    return loadImg(url2) // promise 实例
}).then(img2 => {
    console.log(img2.width)
    return img2
}).then(img2 => {
    console.log(img2.height)
}).catch(ex => console.error(ex))

2、前端使用异步的场景有哪些?

  • 网络请求,如 ajax 图片加载,,动态img加载
  • 定时任务,如 setTimeout setInverval
  • 事件绑定
<!--img加载示例--> 
console.log('start') 
var img=document.createElement('img') 
img.onload=function(){ 
    console.log('loaded') 
 } 
 img.src='/xxx.png'; 
 console.log('end') 
 // start 
 // end 
 // loaded
<!--事件绑定示例-> 
console.log('start') document.getElementById('btn1').addEventListener('click',function(){ 
    alert('clicked') 
 }) 
 console.log('end') 
 // start 
 // end 
 // clicked

知识点:

单线程和异步,异步和同步区别

  • JS是单线程语言,只能同时做一件事
  • 浏览器和NodeJs已支持JS启动进程,如Web Worker
  • JS和DOM渲染共用同一个线程,因为JS可修改DOM结构
  • 遇到等待(网络请求,定时任务)不能卡住
  • 同步会阻塞代码的执行,所以需要异步,解决单线程等待的问题,不会阻塞后面代码的执行(Alert是同步,setTimeout是异步)
  • 回调callback函数形式,setTimeout回调就是异步
// 异步 (callback 回调函数)
 console.log(1)
 setTimeout(function(){
     console.log(2)
 }, 1000)
 console.log(3)
  setTimeout(function(){
     console.log(4)
 }, 0)
 console.log(5)
 //1,3,5,4,2

// 同步,alert点击确定之后才会输出300
console.log(100)
alert(200)
console.log(300)

前端异步的应用场景:

  • 网络请求,如ajax、图片加载img.onload
  • 定时任务,如setTimeout、setInterval
  • 网络请求,如 ajax 图片加载

image.png

image.png

  • 定时任务,如 setTimeout

image.png

callback hell 和 Promise (Promise 解决 callback hell)

  • callback hell是回调嵌套的形式
  • promise实现非嵌套的形式,管道串联的形式,解决callback hell问题,callback嵌套问题
//callback hell
//获取第一份数据
$.get(url1,(data1) => {
	console.log(data1)
	//获取第二份数据
	$.get(url2,(data2) => {
		console.log(data2)
	})
	//获取第三份数据
	$.get(url3,(data3) => {
		console.log(data3)
		//还可能获取更多的数据 -----回调地狱
	})
})
//Promise
function getData(url){
	return new Promise((resolve, reject) => {
		$.ajax({
			url,
			success(data) {
				resolve(data)
			},
			error(err) {
				reject(err)
			}
		})
	})
}
const url1 = '/data1.json'
const url2 = '/data2.json'
const url3 = '/data3.json'
getData(url1).then(data1 => {
	console.log(data1)
	return getData(url2)
}).then(data2 => {
	console.log(data2)
	return getData(url3)
}).then(data3 => {
	console.log(data3)
}).catch(err => console.error(err))

function loadImg(src) {
    const p = new Promise(
        (resolve, reject) => {
            const img = document.createElement('img')
            img.onload = () => {
                resolve(img)
            }
            img.onerror = () => {
                const err = new Error(`图片加载失败 ${src}`)
                reject(err)
            }
            img.src = src
        }
    )
    return p
}

// const url = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
// loadImg(url).then(img => {
//     console.log(img.width)
//     return img
// }).then(img => {
//     console.log(img.height)
// }).catch(ex => console.error(ex))

const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'

loadImg(url1).then(img1 => {
    console.log(img1.width)
    return img1 // 普通对象
}).then(img1 => {
    console.log(img1.height)
    return loadImg(url2) // promise 实例
}).then(img2 => {
    console.log(img2.width)
    return img2
}).then(img2 => {
    console.log(img2.height)
}).catch(ex => console.error(ex))

额外

image.png

日期和math:

获取2017-06-10格式的日期

获取随机数,要求是长度一致的字符串格式

写一个能遍历对象和数组的通用forEach函数

获取2017-06-10格式的日期 
/*2017-06-10*/ 
function formatDate(dt) {     
    if (!dt) {         
        dt = new Date()    
     }    
var year = dt.getFullYear(); 
var month = dt.getMonth() + 1; 
var date = dt.getDate();     
    if (month < 10) {         
        month = '0' + month     
     }     
     if (date < 10) {        
         date = '0' + date     
     }     
return year + '_' + month + '_' + date} 
console.log(formatDate(new Date()))
获取随机数,要求是长度一致的字符串格式 
var random = Math.random(); 
random = random + '0000000000'; 
random = random.slice(0, 10); 
console.log(random)
写一个能遍历对象和数组的通用forEach函数 
function forEach(obj,fn) {     
    if(obj instanceof Array){         
        obj.forEach(function(item,index){             
        fn(index,item)         
        })     
     }else{         
      for(key in obj){             
         if(obj.hasOwnProperty(key)){                 
             fn(key,obj[key])  
          }         
          
     }    
}} 

var arr=[1,2,3]; 
forEach(arr,function(index,item){     
    console.log(index,item)}) 
    
var obj={x:100,y:200} 
forEach(obj,function(key,val){     
    console.log(key,val) 
})

forEach函数.png

五、JS异步进阶

1、请描述event loop(事件循环/事件轮询)的机制,可画图

2、什么是宏任务和微任务,两者有什么区别

3、Promise有哪三种状态?如何变化

4、promise then和catch的连接

// 第一题
Promise.resolve().then(() => {
    console.log(1)
}).catch(() => {
    console.log(2)
}).then(() => {
    console.log(3)
})
// 1 3

// 第二题
Promise.resolve().then(() => {
    console.log(1)
    throw new Error('erro1')
}).catch(() => {
    console.log(2)
}).then(() => {
    console.log(3)
})
// 1 2 3

// 第三题
Promise.resolve().then(() => {
    console.log(1)
    throw new Error('erro1')
}).catch(() => {
    console.log(2)
}).catch(() => { // 注意这里是 catch
    console.log(3)
})
// 1 2

5、async/await 语法问题

async function fn() {
    return 100
}
(async function () {
    const a = fn() // ??               // promise对象
    const b = await fn() // ??         // 100
})()
(async function () {
    console.log('start')
    const a = await 100
    console.log('a', a)
    const b = await Promise.resolve(200)
    console.log('b', b)
    const c = await Promise.reject(300)
    console.log('c', c)
    console.log('end')
})() // 执行完毕,打印出那些内容?//start  a,100  b,200

6、Promise 和 setTimeout 顺序

console.log(100)
setTimeout(() => {
    console.log(200)
})
Promise.resolve().then(() => {
    console.log(300)
})
console.log(400)
// 100 400 300 200

7、async/await的执行顺序问题

async function async1 () {
  console.log('async1 start') //2
  await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行
  console.log('async1 end') //6 await 后面的都作为回调内容---微任务
}

async function async2 () {
  console.log('async2') //3
}

console.log('script start') //1

setTimeout(function () { // 异步,宏任务
  console.log('setTimeout') //8
}, 0)

async1()
// 初始化 promise 时,传入的函数会立刻被执行
new Promise (function (resolve) {
  console.log('promise1') // 4 
  resolve()
}).then (function () { // 异步,微任务
  console.log('promise2') //7
})

console.log('script end') //5

// 同步代码执行完之后,屡一下现有的异步未执行的,按照顺序
// 1. async1 函数中 await 后面的内容 —— 微任务
// 2. setTimeout —— 宏任务
// 3. then —— 微任务



// 同步代码执行完毕 (event loop - call stack 被清空)
// 执行微任务
// (尝试触发DOM渲染)
// 触发Event Loop,执行宏任务 

8、event loop

  • JS是单线程运行的
  • 异步要基于回调来实现
  • event loop就是异步回调的实现原理

JS如何执行

  • 从前到后,一行一行执行
  • 如果某一行执行报错,则停止下面代码的执行
  • 先把同步代码执行完,再执行异步

示例

console.log('Hi')

setTimeout(function cb1() {
    console.log('cb1') // cb 即 callback
}, 5000)

console.log('Bye')

总结event loop的过程

  • 同步代码,一行一行放在Call Stack调用栈执行
  • 遇到异步,会先“记录”下,等待时机(定时、网络请求等)
  • 时机到了,就会移动到Callback Queue
  • 如Call Stack为空(即同步代码执行完)Event Loop开始工作
  • 轮询查找Callback Queue,如有则移动到Call Stack执行
  • 然后继续轮询查找(永动机一样)

DOM事件和event loop

  • JS是单线程的
  • 异步(setTimeOut,ajax等)使用回调,基于event loop
  • Dom事件不是异步,但DOM事件也使用回调,基于event loop
//DOM 事件,也用 event loop
<button id="btn1">提交</button>

<script>
console.log('Hi')

$('#btn1').click(function (e) {
    console.log('button clicked')
})

console.log('Bye')
</script>

image.png

9、promise进阶

三种状态

  • pending resolved rejected
  • pending 》resolved 或 pending 》rejected
  • 变化不可逆

// 刚定义时,状态默认为 pending
const p1 = new Promise((resolve, reject) => {
})
console.log('p1',p1) // pending


// 执行 resolve() 后,状态变成 resolved
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve()
    })
})
console.log('p2',p2) // pending - 开始打印时
setTimeout(() => console.log('p2-setTimeout',p2)) //resolved


// 执行 reject() 后,状态变成 rejected
const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject()
    })
})
console.log('p3',p3) // pending - 开始打印时
setTimeout(() => console.log('p3-setTimeout',p3)) //rejected

image.png

状态的表现和变化

  • pending状态,不会触发then和catch
  • resolved状态,会触发后续的then回调函数
  • rejected状态,会触发后续的catch回调函数
const p1 = Promise.resolve(100)
console.log('p1',p1)
const p2 = Promise.reject('err')
console.log('p2',p2)

image.png

const p1 = Promise.resolve(100) //resolved
//console.log('p1',p1)
p1.then(data => {
	console.log('data',data)
}).catch(err => {
	console.error('err',err)
})
const p2 = Promise.reject('err') //rejected
//console.log('p2',p2)
p2.then(data => {
	console.log('data2',data)
}).catch(err => {
	console.error('err2',err)
})

image.png

then和catch对状态的影响(then和catch改变状态)

then catch 会继续返回 Promise ,此时可能会发生状态变化!!!

  • then正常返回resolved,里面有报错则返回rejected
  • catch正常返回resolved,里面有报错则返回rejected
const p1 = Promise.resolve().then(() => {
    return 100
})
console.log('p1',p1) //resolved
const p2 = Promise.resolve().then(() => {
    throw new Error('then error')
})
console.log('p2',p2) //rejected

image.png

const p1 = Promise.resolve().then(() => {
    return 100
})
console.log('p1',p1) //resolved 触发后续then回调
p1.then(() => {
	console.log('123')
})
const p2 = Promise.resolve().then(() => {
    throw new Error('then error')
})
console.log('p2',p2) //rejected 触发后续catch回调
p2.then(() => {
	console.log('456')
}).catch(err => {
	console.error('err100',err)
})

image.png

const p3 = Promise.reject('my error').catch((err) => {
    console.error('err)
})
console.log('p3',p3) //resolved 注意!触发then函数
p3.then(() => {
	console.log(100)
})
const p4 = Promise.reject(('my error').catch((err) => {
    throw new Error('catch err')
})
console.log('p4',p4) //rejected 触发后续catch回调
p4.then(() => {
	console.log(200)
}).catch(()=> {
	console.error('some err')
})

image.png

// then() 一般正常返回 resolved 状态的 promise
Promise.resolve().then(() => {
    return 100
})

// then() 里抛出错误,会返回 rejected 状态的 promise
Promise.resolve().then(() => {
    throw new Error('err')
})

// catch() 不抛出错误,会返回 resolved 状态的 promise
Promise.reject().catch(() => {
    console.error('catch some error')
})

// catch() 抛出错误,会返回 rejected 状态的 promise
Promise.reject().catch(() => {
    console.error('catch some error')
    throw new Error('err')
})

// 第一题
Promise.resolve().then(() => {
    console.log(1) //1
}).catch(() => {
    console.log(2)
}).then(() => {
    console.log(3) //3
})

// 第二题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
    console.log(1) //1
    throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
    console.log(2) //2
}).then(() => {
    console.log(3) //3
})

// 第三题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
    console.log(1) //1
    throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
    console.log(2) //2
}).catch(() => {
    console.log(3)
})

Promise总结

  • 三种状态,状态的表现和变化
  • then和catch对状态的影响
  • then和catch的链式调用

10、async/await

  • 异步调用callback hell
  • Promise then catch链式调用,但也是基于回调函数
  • async/await是同步语法实现异步,彻底消灭回调函数
function loadImg(src) {
    const promise = new Promise((resolve, reject) => {
        const img = document.createElement('img')
        img.onload = () => {
            resolve(img)
        }
        img.onerror = () => {
            reject(new Error(`图片加载失败 ${src}`))
        }
        img.src = src
    })
    return promise
}
const src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
const src2 = 'https://avatars3.githubusercontent.com/u/9583120'
(async function () {
    // 注意:await 必须放在 async 函数中,否则会报错
    // 加载第一张图片
    const img1 = await loadImg(src1)
    console.log(img1)
    // 加载第二张图片
    const img2 = await loadImg(src2)
    console.log(img2)
})()

image.png 因为src2后面没加分号,和后面的()连在一起被当做函数,类似图示alert也会弹出弹框,解决办法:(async前面加!号

const src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
const src2 = 'https://avatars3.githubusercontent.com/u/9583120'
!(async function () {
    // 注意:await 必须放在 async 函数中,否则会报错
    // 加载第一张图片
    const img1 = await loadImg(src1)
    console.log(img1)
    // 加载第二张图片
    const img2 = await loadImg(src2)
    console.log(img2)
})()
//用同步的方式,编写异步。
async function loadImg1() {
    const src1 = 'http://www.imooc.com/static/img/index/logo_new.png'
    const img1 = await loadImg(src1)
    return img1
}

async function loadImg2() {
    const src2 = 'https://avatars3.githubusercontent.com/u/9583120'
    const img2 = await loadImg(src2)
    return img2
}

!(async function () {
    // 注意:await 必须放在 async 函数中,否则会报错
    try {
        // 加载第一张图片
        const img1 = await loadImg1() //await后面不仅可以加promise对象也可以加async函数
        console.log(img1)
        // 加载第二张图片
        const img2 = await loadImg2()
        console.log(img2)
    } catch (ex) {
        console.error(ex)
    }
})()

async-await和Promise有什么关系

  • async/await是消灭异步回调的终极武器
  • 但和Promise并不互斥
  • 反而,两者相辅相成
  • 执行async函数,返回的是Promise对象,如果函数内没返回 Promise ,则自动封装成Promise对象
async function fn1() {
    return new Promise(200)
}
const res1 = fn1()//执行async函数,返回的是一个Promise对象
res1.then(data => {console.log('data',data)}) //200

async function fn2() {
    return 100 // 相当于 Promise.resolve(100)
}
console.log( fn2() ) //Promise对象
  • await 相当于Promise的then,await 后面可以加promise对象、值、async函数的执行结果
// await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 resolved ,才获取结果并继续执行
// await 后续跟非 Promise 对象:会直接返回
!(async function () {
    const p1 = Promise.resolve(100)
    const data = await p1 // await 相当于Promise的then
    console.log('data',data) // 100
})()
!(async function () {
    const data1 = await 400 // await Promise.resolve(400)
    console.log('data1',data1)
})()
!(async function () {
    const data2 = await fn1() 
    console.log('data2',data2)
})()

!(async function () {
    const p1 = new Promise(() => {})
    await p1
    console.log('p1') // 不会执行
})()
!(async function () {
    const p4 = Promise.reject('some err') //rejected状态
    const res = await p4 // await -> then
    console.log(res) // 不会执行
})()
  • try…catch可捕获异常,代替了Promise的catch
!(async function () {
    const p4 = Promise.reject('some err') //rejected状态
    try {
        const res = await p4
        console.log(res)
    } catch (ex) {
        console.error(ex) // try...catch 相当于 promise catch
    }
})()
//总结
async 封装 Promise
await 处理 Promise 成功
try...catch 处理 Promise 失败

async/await是语法糖,异步的本质还是回调函数

  • async/await是消灭异步回调的终极武器
  • JS是单线程,还得是有异步,还得是基于event loop
  • async/await只是一个语法糖,但这颗糖真香
//只要遇到了 await ,后面的代码都相当于放在 callback 里
async function async1 () {
  console.log('async1 start') // 2
  await async2() //undefined
  // await 的后面都可以看做是calllback里的内容,即异步
  // 类似,event loop,setTimeout(cb1)
  // setTimeout(function() { console.log('async1 end') })
  // Promise.resolve().then(() => { console.log('async1 end') } ) //微任务/宏任务
  console.log('async1 end') // 5 关键在这一步,它相当于放在 callback 中,最后执行
}

async function async2 () {
  console.log('async2') // 3
}

console.log('script start') //1
async1() //立马执行async1函数体
console.log('script end') //4
// 同步代码已经执行完(event loop)
async function async1 () {
  console.log('async1 start') // 2
  await async2()
  // 下面三行都是异步回调 callback的内容
  console.log('async1 end') // 5
  await async3()
      console.log('async1 end 2')} //7

async function async2 () {
  console.log('async2') // 3
}

async function async3 () {
  console.log('async3') // 6
}
console.log('script start') //1
async1()
console.log('script end') //4
// 同步代码已经执行完(event loop)

for…of

  • for…in(以及forEach for) 是常规的同步遍历
  • for…of常用于异步的遍历
// 定时算乘法
function multi(num) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(num * num)
        }, 1000)
    })
}

// // 使用 forEach ,是 1s 之后打印出所有结果,即 3 个值是一起被计算出来的
// function test1 () {
//     const nums = [1, 2, 3];
//     nums.forEach(async x => {
//         const res = await multi(x);
//         console.log(res);
//     })
// }
// test1();

// 使用 for...of ,可以让计算挨个串行执行
async function test2 () {
    const nums = [1, 2, 3];
    for (let x of nums) {
        // 在 for...of 循环体的内部,遇到 await 会挨个串行计算
        const res = await multi(x)
        console.log(res)
    }
}
test2()

11、微任务microTask和宏任务macroTask

  • 什么是宏任务,什么是微任务
  • event loop和DOM渲染
  • 微任务和宏任务的区别
console.log(100)
setTimeout(() => {
    console.log(200)
})
Promise.resolve().then(() => {
    console.log(300)
})
console.log(400)
// 100 400 300 200

宏任务和微任务

  • 宏任务:setTimeout,setInterval,Ajax,DOM事件
  • 微任务:Promise async/await
  • 微任务执行时机比宏任务要早(先记住)

event loop和DOM渲染

  • 再次回归一遍event loop的过程

image.png

  • JS是单线程的,而且和DOM渲染共用一个线程
  • JS执行的时候,得留一些时机供DOM渲染
  • 回顾event loop过程(增加DOM渲染时机)

image.png

1.每次Call Stack清空(即每次轮询结束),即同步任务执行完,或者说异步代码推到Call Stack执行结束
2.都是DOM重新渲染的机会,DOM结构如有改变则重新渲染,(不一定非得渲染,就是给一次 DOM 渲染的机会!!!)
3.然后再去触发下一次Event Loop
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
            .append($p1)
            .append($p2)
            .append($p3)

console.log('length',  $('#container').children().length )
alert('本次 call stack 结束,DOM 结构已更新,但尚未触发渲染')
// (alert 会阻断 js 执行,也会阻断 DOM 渲染,便于查看效果)
// 到此,即本次 call stack 结束后(同步任务都执行完了),浏览器会自动触发渲染,不用代码干预

// 微任务:DOM渲染前触发
Promise.resolve().then(() => {
 console.log('length1',$('#container').children().length) //3
alert('Promise then') //DOM渲染了吗?NO
})
// 宏任务:DOM渲染后触发
setTimeout(() => {
 console.log('length2',$('#container').children().length) //3
alert('setTimeout') //DOM渲染了吗?yes
})

宏任务和微任务的区别

  • 宏任务:DOM渲染后触发,如setTimeout
  • 微任务:DOM渲染前触发,如Promise

从event loop解释,为何微任务执行更早

  • 宏任务(浏览器规定的) image.png image.png
  • 微任务(ES6语法规定的) image.png image.png
微任务:ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何关于,即可一次性处理完,更快更及时。
宏任务:ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。

六、JS-WEB-API

JS基础知识,规定语法(ECMA 262 标准)

JS Web API ,网页操作的API (W3C标准)

前者是后者的基础,两者结合才能真正实际应用

1、DOM

前言

  • vue 和 React 框架应用广泛, 封装了 DOM 操作
  • 但 DOM 操作一直都会是前端工程师的基础,必备知识
  • 只会 vue 而不懂 DOM 操作的前端程序员,不会长久

DOM 操作文档对象模型

1)、DOM 是哪种数据结构

HTML文件解析出来的一棵树

2)、DOM 操作的常用API

获取DOM节点,以及节点的property和Attribute

获取父节点,获取子节点

新增节点,删除节点

3)、DOM节点的Attribute和property有何区别

Property修改对象属性,不会体现到html结构中(尽量使用Property,而非Attribute)

Attribute修改html属性,会改变html结构

两者都有可能引起 DOM 重新渲染 (推荐用property,有重复操作可能不会引起不必要的渲染。DOM的重新渲染耗费性能)

4)、一次性插入多个DOM 节点,考虑性能

DOM操作非常‘昂贵’,避免频繁DOM操作

对DOM查询做缓存

将频繁操作改为一次性操作
const list = document.getElementById('list')

// 创建一个文档片段,此时还没有插入到 DOM 结构中
const frag = document.createDocumentFragment()

for (let i  = 0; i < 20; i++) {
    const li = document.createElement('li')
    li.innerHTML = `List item ${i}`

    // 先插入文档片段中
    frag.appendChild(li)
}

// 都完成之后,再统一插入到 DOM 结构中
list.appendChild(frag)

console.log(list)

知识点:

DOM 节点操作

  <body>
        <div id="div1" class="container">
            <p id="p1">一段文字 1</p>
            <p>一段文字 2</p>
            <p>一段文字 3</p>
        </div>
        <div id="div2">
            <img src="https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg"/>
        </div>
        <ul id="list">
        </ul>

        <script src="./dom-3.js"></script>
    </body>
    
    
 // 1.获取dom节点:   
// const div1 = document.getElementById('div1')
// console.log('div1', div1)

// const divList = document.getElementsByTagName('div') // 集合
// console.log('divList.length', divList.length)
// console.log('divList[1]', divList[1])

// const containerList = document.getElementsByClassName('container') // 集合
// console.log('containerList.length', containerList.length)
// console.log('containerList[1]', containerList[1])



// 2.attribute

//通过修改或者获取js的属性来改变页面样式、页面渲染结构的一种形式,Dom结构js变量的修改
// const pList = document.querySelectorAll('p')
// console.log('pList', pList)

// const pList = document.querySelectorAll('p')
// const p1 = pList[0]


// 修改标签的属性,Dom结构节点属性修改
// p1.style.width = '100px'
// console.log( p1.style.width )// 获取样式
// p1.style.width = '100px'    // 修改样式
// p1.className = 'red' // 修改 class
// console.log( p1.className )  // 获取 class
// console.log(p1.nodeName)
// console.log(p1.nodeType) // 1


// // 3.property 形式
// const pList = document.querySelectorAll('p')
// const p1 = pList[0]
// p1.setAttribute('data-name', 'baidu')
// console.log( p1.getAttribute('data-name') )
// p1.setAttribute('style', 'font-size: 50px;')
// console.log( p1.getAttribute('style') )

  • 总结
property:修改对象属性,不会体现到html结构中
attribute:修改html属性,会改变html结构
两者都有可能引起DOM重新渲染
最好使用property

DOM 结构操作

DOM 结构操作

const div1 = document.getElementById("div1"); 
const div2 = document.getElementById("div2"); 

// 新建节点 
const newP = document.createElement("p"); 
newP.innerHTML = "this is newP"; 
// 插入节点 
div1.appendChild(newP); 

// 移动节点 
const p1 = document.getElementById("p1"); 
div2.appendChild(p1); 

// 获取父元素
console.log(p1.parentNode); 

// 获取子元素列表 
const div1ChildNodes = div1.childNodes; 
console.log(div1.childNodes); //还会含有text文本标签,nodeType 等于3

// 子节点会包括标签和文本,  利用 filter() 过滤  
// text 的 nodeText = 3
const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes)
    .filter((child) => { 
        if (child.nodeType === 1) { 
                return true; 
            } 
    return false; 
    }); 
console.log("div1ChildNodesP", div1ChildNodesP); 

// 删除节点 
div1.removeChild(div1ChildNodesP[0]);

DOM 性能优化

  • DOM 操作非常"昂贵",避免频繁操作

  • 对 DOM 查询做缓存

//不缓存 DOM 查询结果
for (let i = 0; i < document.getElementsByTagName('p').length; i++){
	//每次循环,都会计算length,频繁进行DOM查询
}
//缓存 DOM 查询结果
const pList = document.getElementsByTagName('p')
const length = pList.length
for (let i = 0; i < length ; i++){
	//缓存length,只进行一次DOM查询
}
  • 将频繁操作改为一次性操作
const list = document.getElementById('list')

// 创建一个文档片段,此时还没有插入到 DOM 结构中
const frag = document.createDocumentFragment()

for (let i  = 0; i < 20; i++) {
    const li = document.createElement('li')
    li.innerHTML = `List item ${i}`

    // 先插入文档片段中
    frag.appendChild(li)
}

// 都完成之后,再统一插入到 DOM 结构中
list.appendChild(frag)

console.log(list)

2、BOM

BOM 操作(Browser Object Model)l浏览器对象模型

常考:

如何检测浏览器的类型 /navigator(浏览器信息)

Var ua=navigator.userAgent 
Var isChrome=ua.indexOf(‘Chrome’) 
Console.log(isChrome) 

screen:屏幕大小 /(屏幕信息,例如宽度高度等)
console.log(screen.width); 
console.log(screen.height);
// 分析拆解 url 各个部分/location(地址信息)
console.log(location.href)
console.log(location.protocol) //http:或https:
console.log(location.pathname) // '/learn/1234'
console.log(location.search)
console.log(location.hash)
console.log(location.host) 域名

// history:(前进后退信息)
history.back() 
history.forward()

代码演示:

navigator.userAgent

'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'

location.protocol---协议

https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=react&fenlei=256&rsv_pq=8a1172e50002b015&rsv_t=0115DKEg3U177v8B%2Fl9X8F%2B7mKJpeR8iorcE4T%2BNwOsH9sG0ATAVZIksDN1N&rqlang=en&rsv_enter=1&rsv_dl=tb&rsv_sug3=6&rsv_sug1=3&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&inputT=2772&rsv_sug4=3033
// 输出:'https:'

location.host--域名

 www.baidu.com

location.pathname

  '/s'

3、事件

1) 事件绑定

const btn = document.getElementById('btn1')
btn.addEventListener('click', event => {
	console.log('clicked')
})
//通用的绑定函数
function bindEvent(elem,type,fn){
	elem.addEventListener(type,fn)
}
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', (e)=> {
    // console.log(event.target) // 获取触发的元素
    event.preventDefault() // 阻止默认行为
    alert('clicked')
})

2)事件冒泡---从下向上进行冒泡

描述事件冒泡的流程

//描述事件冒泡的流程 
基于DOM树形结构 
事件会顺着触发元素往上冒泡 
应用场景:代理----工作中经常用到

代理的好处
1.代码简洁
2、减少浏览器内存占用
<body>
	<div id="div1">
		<p id="p1">激活</p>
		<p id="p2">取消</p>
		<p id="p3">取消</p>
		<p id="p4">取消</p>
	</div>
	<div id="div2">
		<p id="p5">取消</p>
		<p id="p6">取消</p>
	</div>
</body>

const p1 = document.getElementById('p1')
const body = document.body
bindEvent(p1,'click',e => {
	e.stopPropagation() //阻止冒泡,可以注释这一行,来体会事件冒泡,没有这一行的话会先弹激活,再弹取消,有这一行只弹激活
	alert('激活')
})
bindEvent(body,'click',e => {
	alert('取消')
})

3)事件代理/编写一个通用的事件监听函数

面试让写了

  • 代码简洁、减少浏览器内存占用、但是不要滥用
// event.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>事件 演示</title>
        <style>
            div {
                border: 1px solid #ccc;
                margin: 10px 0;
                padding: 0 10px;
            }
        </style>
    </head>
    <body>
        <button id="btn1">一个按钮</button>

        <!-- <div id="div1">
            <p id="p1">激活</p>
            <p id="p2">取消</p>
            <p id="p3">取消</p>
            <p id="p4">取消</p>
        </div>
        <div id="div2">
            <p id="p5">取消</p>
            <p id="p6">取消</p>
        </div> -->

        <div id="div3">
            <a href="#">a1</a><br>
            <a href="#">a2</a><br>
            <a href="#">a3</a><br>
            <a href="#">a4</a><br>
            <button>加载更多...</button>
        </div>

        <script src="./event.js"></script>
    </body>
</html>
// event.js
// 通用的事件绑定函数
// function bindEvent(elem, type, fn) {
//     elem.addEventListener(type, fn)
// }
function bindEvent(elem, type, selector, fn) {
    if (fn == null) {
        fn = selector
        selector = null
    }
    elem.addEventListener(type, event => {
        const target = event.target
        if (selector) {
            // 代理绑定
            if (target.matches(selector)) {
                fn.call(target, event)
            }
        } else {
            // 普通绑定
            fn.call(target, event)
        }
    })
}

// 普通绑定
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', function (event) {
    // console.log(event.target) // 获取触发的元素
    event.preventDefault() // 阻止默认行为
    alert(this.innerHTML)
})

// 代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3, 'click', 'a', function (event) {
    event.preventDefault()
    alert(this.innerHTML)
})

// const p1 = document.getElementById('p1')
// bindEvent(p1, 'click', event => {
//     event.stopPropagation() // 阻止冒泡
//     console.log('激活')
// })
// const body = document.body
// bindEvent(body, 'click', event => {
//     console.log('取消')
//     // console.log(event.target)
// })
// const div2 = document.getElementById('div2')
// bindEvent(div2, 'click', event => {
//     console.log('div2 clicked')
//     console.log(event.target)
// })

无限下拉的图片列表,如何监听每个图片的点击

//无限下拉的图片列表,如何监听每个图片的点击 
使用事件代理 
用e.target获取触发元素 
用matches来判断是否是触发元素

4、Ajax

1)手动编写一个ajax(面试常问)

function ajax(url,successFn) {
     const xhr = new XMLHttpRequest()
     xhr.open('GET', url, true)
     xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    successFn(xhr.responseText)
                }
            }
    }
    xhr.send(null)
}
// get 请求
const xhr = new XMLHttpRequest()
// /api 如 /data/text.json
// 第三个参数 表示是否异步,true 为异步请求  false 为同步
xhr.open("GET", "/api", true)
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            // 转换成 json 格式
            // console.log(
            //     JSON.parse(xhr.responseText)
            // )
            console.log(xhr.responseText)
        } else {
            console.log("其他情况")
        }
    }
}
xhr.send(null)

// post 请求
const xhr = new XMLHttpRequest()
xhr.open("POST", "/login", true)
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            console.log(xhr.responseText)
        } else {
            console.log("其他情况")
        }
    }
}
cosnt postData = {
	userName: 'zhangsan',
	passworad: 'xxx'
}
// post 请求发送字符串
xhr.send(JSON.stringify(postData))

// ajax.html文件---完整
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>ajax 演示</title>
    </head>
    <body>
        <p>一段文字 1</p>

        <script src="./ajax.js"></script>
    </body>
</html>

// ajax.js文件
// XMLHttpRequest示例: 手动编写一个ajax
// const xhr = new XMLHttpRequest()
// xhr.open('GET', '/data/test.json', true)
// xhr.onreadystatechange = function () {
//  这里的函数异步执行,可参考之前JS基础的异步模板
//     if (xhr.readyState === 4) {
//         if (xhr.status === 200) {
//             // console.log(
//             //     JSON.parse(xhr.responseText)
//             // )
//             alert(xhr.responseText)
//         } else if (xhr.status === 404) {
//             console.log('404 not found')
//         }
//     }
// }
// xhr.send(null)

function ajax(url) {
    const p = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.open('GET', url, true)
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    resolve(
                        JSON.parse(xhr.responseText)
                    )
                } else if (xhr.status === 404 || xhr.status === 500) {
                    reject(new Error('404 not found'))
                }
            }
        }
        xhr.send(null)
    })
    return p
}

const url = '/data/test.json'
ajax(url)
.then(res => console.log(res))
.catch(err => console.error(err))

2)跨域的常用实现方式、跨域的原理是什么 (面试常问)

  1. JSONP
  2. CORS

跨域:同源策略,跨域解决方案

  • 什么是跨域(同源策略)
//同源策略
	//1.ajax请求时,浏览器要求当前网页和server必须同源(安全)
	//2.同源:协议、域名、端口,三者必须一致
	//3.前端:http://a.com:8080/;server:https://b.com/api/xxx(线上默认80端口)
//加载图片 css js 可无视同源策略
	//1.<img src=跨域的图片地址 />
	//2.<link href=跨域的css地址 />
	//3.<script src=跨域的js地址></script>

//加载图片 css js 可无视同源策略
	//1.<img />可用于统计打点,可使用第三方统计服务
	//2.<link /><script />可使用CDN,CDN一般都是外域
	//3.<script>可实现JSONP
//跨域
	//1.所有的跨域,都必须经过server端允许和配合
	//2.未经server端允许就实现跨域,说明浏览器有漏洞,危险信号
  • JSONP
//访问https://www.taobao.com/,服务端一定返回一个html文件吗?
//服务器可以任意动态拼接数据返回,只要符合html格式要求
//同理于<script src="https://www.taobao.com/getData.js">
//<script>可绕过跨域限制
  //服务器可以任意动态拼接数据返回
  //所以,<script>就可以获得跨域的数据,只要服务端愿意返回
// jsonp.html
<body>
        <p>一段文字 1</p>

        <script>
            window.abc = function (data) {
            	//这是我们跨域得到的信息
                console.log(data)//{ name: 'xxx' }
            }
        </script>
        <script src="http://localhost:8002/jsonp.js?username=xxx&callback=abc"></script>
    </body>
// jsonp.js
abc(
    { name: 'xxx' }
)
//jQuery实现jsonp
$.ajax({
    url:'http://localhost:8882/x-origin.json',
    dataType:"jsonp",
    jsonpCallback:"callback",
    success:function(data){
        console.log(data);
    }
});


  • CORS(服务端支持)--服务器设置http header
//第二个参数填写允许跨域的域名称,不建议直接写“*”
response.setHeader("Access-Control-Allow-Origin","http://locaohost:8011")
response.setHeader("Access-Control-Allow-Headers","X-Requested-With")
response.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS")
//接受跨域的cookie
response.setHeader("Access-Control-Allow-Credentials","true")

3)状态码

readyState

  • 0 - (未初始化) 还没有调用send() 方法
  • 1 - (载入) 已调用 send() 方法,正在发送请求
  • 2 - (载入完成) send() 方法执行完成,已经接受到全部响应内容
  • 3 - (交互) 正在解析响应内容
  • 4 - (完成) 响应内容解析完成,可以在客户端调用

状态码说明

  • 1xx 服务器收到请求
  • 2xx - 表示成功处理请求,如 200
  • 3xx - 需要重定向,浏览器直接跳转,如 301 302 304
  • 4xx - 客户端请求错误,如404 403
  • 5xx - 服务器端错误

常见状态码

  • 200 成功
  • 301 永久重定向(配合location,浏览器自动处理)
  • 302 临时重定向(配合location,浏览器自动处理)
  • 304 资源未被修改,请求资源和之前资源一样
  • 404 资源未找到
  • 403 没有权限
  • 500 服务器错误
  • 504 网关超时,一台服务器请求另一台服务器时出错

5、存储

描述 cookie localStorage sessionStorage 区别

  • 容量
  • API 易用性
  • 是否跟随 http 请求发送出去
特性cookielocalStoragesessionStorageindexDB
数据生命周期一般由服务器生成,可以设置过期时间除非被清理,否则一直存在页面关闭就清理除非被清理,否则一直存在
数据存储大小4K5M5M无限
与服务端通信每次都会携带在 header 中,对于请求性能影响不参与不参与不参与

从上表可以看到,cookie 已经不建议用于存储。如果没有大量数据存储需求的话,可以使用 localStoragesessionStorage 。对于不怎么改变的数据尽量使用 localStorage 存储,否则可以用 sessionStorage 存储。

对于 cookie,我们还需要注意安全性。

属性作用
value如果用于保存用户登录态,应该将该值加密,不能使用明文的用户标识
http-only不能通过 JS 访问 Cookie,减少 XSS 攻击
secure只能在协议为 HTTPS 的请求中携带
same-site规定浏览器不能在跨域请求中携带 Cookie,减少 CSRF 攻击

cookie:

本身用于浏览器和server通讯
被“借用”到本地存储来
可用document.cookie = '...'来修改
document.cookie = 'a=100;b=200;'
document.cookie //a=100
document.cookie = 'b=200;'
document.cookie //a=100; b=200
document.cookie = 'a=300;'
document.cookie //b=200; a=300
document.cookie = 'd=400;'
document.cookie //b=200; a=300;d=400;

每次添加一条数据,相同的key 会覆盖,不同的key 会追加。

有限制,不太好用
//cookie缺点
存储大小,最大4KB
http请求时需要发送到服务端,增加请求数据量
只能用document.cookie = '...'来修改,太过简陋

localStorage 和 sessionStorage

HTML5专门为存储而设计,最大可存5M
API简单易用setItem,getItem
不会随着http请求被发送出去

image.png

七、HTTP协议

http methods

传统的methods

  • get请求服务器数据
  • post向服务器提交数据
  • 简单的网页功能,就这两个操作

现在的methods

  • get获取数据
  • post新建数据
  • patch/put更新数据
  • delete删除数据

什么是Restful API

一种新的API设计方法

传统API设计:把每个url当做一个功能

Restful API设计:把每个url当做一个唯一的资源

如何设计成一个资源

1.尽量不用url参数

传统的API设计:/api/list?pageIndex=2
 Restful API设计:/api/list/2

2.用method表示操作类型

  • 传统的API设计:
post请求:/api/create-blog

post请求:/api/update-blog?id=100

get请求:/api/get-blog?id=100
  • Restful API设计:
post请求:/api/blog

patch请求:/api/blog/100

get请求:/api/blog/100

http常见的header有哪些

常见的Request Headers

Accept 浏览器可接收的数据格式
Accept-Encoding 浏览器可接收的压缩算法,如gzip
Accept-Languange 浏览器可接收的语言 ,如zh-CN
Connection:keep-alive一次TCP连接重复使用
cookie
Host
User-Agent(简称UA)浏览器信息
Content-type 发送数据的格式,如application/json

常见的Response Headers

Content-type 返回数据的格式,如application/json
Content-length 返回数据的大小,多少字节
Content-Encoding 返回数据的压缩算法,如gzip
Set-Cookie

自定义header

headers:{"X-Requested-With":"XMLHttpRequest"}

缓存相关的headers

Cache-Control
Expires
Last-Modified
If-Modified-Since
Etag
If-None-Match

http缓存

http缓存攻略(强制缓存 + 协商缓存)
刷新操作方式,对缓存的影响
静态资源(js、css、img)可以被缓存

描述一下http的缓存机制(重要)

强制缓存

image.png

缓存过期

image.png

请求缓存的情况 image.png

Cache-Control

Response Headers中
控制强制缓存的逻辑
例如Cache-Control:max-age=31536000(单位是秒)

Cache-Control的值

max-age
no-cache:不用强制缓存,我们到服务端去请求,服务端怎么处理我们不管
no-store:我们不用强制缓存,而且我们也不用服务端的一些缓存措施,让服务端简单粗暴的将资源再返回我一份
private:最终用户做缓存,比如电脑,浏览器,手机
public:中间的路由,中间的代理也可以作为缓存

关于Expires

同在Response Headers中
同为控制强制缓存的过期
已被Cache-Control代替

协商(对比)缓存

  • 服务器端缓存策略:服务端来判断一个资源是不是被缓存
  • 服务器判断客户端资源,是否和服务端资源一样
  • 一致则返回304,否则返回200和最新的资源

image.png

资源标识

在Response Headers中,有两种
Last-Modified 资源的最后修改时间
Etag 资源的唯一标识 (一个字符串,类似人类的指纹)

Last-Modified image.png

Etag image.png

Headers示例 image.png

请求示例 image.png Last-Modified和Etag

会优先使用Etag
Last-Modified只能精确到秒级,而计算机大多毫秒级以内
如果资源被重复生成,而内容不变,则Etag更精确

http缓存综述流程图 image.png

刷新页面对http缓存的影响

三种刷新操作

  • 正常操作:地址栏输入url,跳转链接,前进后退等
  • 手动刷新:F5,点击刷新按钮,点击菜单刷新
  • 强制刷新:Ctrl + F5

不同刷新操作,不同的缓存策略

  • 正常操作:强制缓存有效,协商缓存有效
  • 手动刷新:强制缓存失效,协商缓存有效
  • 强制刷新:强制缓存失效,协商缓存失效

八、运行环境

1、页面加载

  • 即浏览器
  • 下载网页代码,渲染出页面,期间会执行若干JS
  • 要保证代码在浏览器中,稳定且高效

1)、从输入 url 到渲染出页面的整个过程

浏览器根据DNS服务器得到域名的IP地址

向这个IP的机器发送http请求

服务器收到、处理并返回http请求

浏览器得到返回内容

2)、window.onload 和 DOMContentLoaded 的区别

Window.onload:页面的全部资源加载完才会执行,包括图片、视频等

DOMContentLoaded:DOM渲染完即可执行,此时图片、视频还没有加载完

   window.addEventListener('load', function () {
     // 页面的全部资源加载完才会执行,包括图片、视频等
        console.log('window loaded')
    })

    document.addEventListener('DOMContentLoaded', function () {
     // DOM渲染完即可执行,此时图片、视频还没有加载完
        console.log('dom content loaded')
    })

知识点

加载资源的过程

  • DNS解析(域名服务解析):域名->IP地址,域名统一好记,IP地址不同区域因代理可能不一致不好标识,要转IP地址是因为浏览器真正访问时访问的是IP地址
  • 浏览器根据IP地址向服务器发起http请求
  • 服务器处理http请求,并返回给浏览器

渲染页面的过程

  • 根据 HTML 代码生成 DOM Tree
  • 根据 CSS 代码生成 CSSOM
  • 将 DOM Tree 和CSSOM 整合生成 Render Tree
  • 根据 Render Tree 渲染页面
  • 遇到 < script> 则暂停渲染,优先加载并执行 JS 代码,完成在继续
  • 直至把 Render Tree 渲染完成

2、性能优化

是一个综合性问题,没有标准答案,但要求尽量全面

细节问题:手写防抖、节流

1)性能优化原则

  • 多使用内存、缓存或其他方法
  • 减少CPU计算量,减少网络加载耗时
  • 适用于所有编程的性能优化,空间换时间

2)方法 ( 两个方面)

a.让加载更快

  • 减少资源体积,压缩代码,例如:webpack,服务器端也会进行gzip压缩,大约会压缩三分之一
  • 减少访问次数,合并代码(精灵图、雪碧图、webpack),SSR服务端渲染,缓存(webpack的output加contenthash产生数字文件主要有这个效果)
  • 使用更快的网络,CDN

缓存

  • 静态资源加hash后缀,根据文件内容计算hash;
  • 文件内容不变,则hash不变,则url不变
  • url和文件不变,则会自动触发http缓存机制,返回304

image.png

SSR

  • 服务端渲染:将网页和数据一起加载,一起渲染
  • 非SSR(前后端分离):先加载网页,再加载数据,再渲染数据
  • 早先的JSP、ASP、PHP,现在的Vue React SSR借助一些Node的能力来做

b.让渲染更快

  • css放在head中,js放在body最后
  • 尽早开始执行JS,用DOMContentLoaded触发
  • 懒加载 (图片懒加载,上滑加载更多)
  • 对 DOM 查询进行缓存,for循环中,先缓存DOM查询结果,缓存length
  • 频繁 DOM 操作,合并到一起插入 DOM 结构

懒加载:

 懒加载
 // const img = document.getElementById('img1')
 // img1.src=img1.getAttribute('data-realsrc')

image.png

缓存 DOM 查询

image.png

多个 DOM 操作一起插入到DOM 结构

const list = document.getElementById('list')

// 创建一个文档片段,此时还没有插入到 DOM 结构中
const frag = document.createDocumentFragment()

for (let i  = 0; i < 20; i++) {
    const li = document.createElement('li')
    li.innerHTML = `List item ${i}`

    // 先插入文档片段中
    frag.appendChild(li)
}

// 都完成之后,再统一插入到 DOM 结构中
list.appendChild(frag)

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>运行环境 演示</title>
    </head>
    <body>
        <p>一段文字 1</p>
        <p>一段文字 2</p>
        <p>一段文字 3</p>
        <img
            id="img1"
            src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1570191150419&di=37b1892665fc74806306ce7f9c3f1971&imgtype=0&src=http%3A%2F%2Fimg.pconline.com.cn%2Fimages%2Fupload%2Fupc%2Ftx%2Fitbbs%2F1411%2F13%2Fc14%2F26229_1415883419758.jpg"
        />

        <script src="./index.js"></script>
    </body>
</html>
const img1 = document.getElementById('img1')
img1.onload = function () {
    console.log('img loaded')
}
//页面的全部资源加载完才会执行,包括图片、视频等
window.addEventListener('load', function () {
    console.log('window loaded')
})
//DOM渲染完即可执行,此时图片、视频可能还没有加载完
document.addEventListener('DOMContentLoaded', function () {
    console.log('dom content loaded')
})

3)节流 throttle 防抖 debounce (用户体验)

当一个函数被频繁、无限制的被调用的时候,会加重浏览器的负担,造成浏览器卡顿的现象。

例如:常用浏览器滚动scroll,鼠标移动onmousemove事件触发的事件。因此引入了防抖和节流两个概念来解决这个问题。

防抖 debounce

场景

  • 监听一个输入框,文字变化后触发change事件
  • 直接用keyup事件,会频发触发change事件
  • 防抖:用户输入结束或暂停时,才会触发change事件
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>debounce 演示</title>
    </head>
    <body>
        <input type="text" id="input1">

        <script src="./debounce.js"></script>
    </body>
</html>

const input1 = document.getElementById('input1')

// let timer = null
// input1.addEventListener('keyup', function () {
//     if (timer) {
//         clearTimeout(timer)
//     }
//     timer = setTimeout(() => {
//         // 模拟触发 change 事件
//         console.log(input1.value)

//         // 清空定时器
//         timer = null
//     }, 500)
// })

// 防抖
function debounce(fn, delay = 500) {
    // timer 是闭包中的
    let timer = null

    return function () {
        if (timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}

input1.addEventListener('keyup', debounce(function (e) {
    console.log(e.target)
    console.log(input1.value)
}, 600))

防抖:当持续触发事件时,指定时间(自定义)没有再触发事件,事件处理函数才会执行一次。如果小于指定。时间触发事件,事件处理函数不会被执行,而是以最后一次触发的事件,重新计算,即:最后时间点后指定时间,触发事件。

通俗理解:停止动作后(时间点)+ 自定义时间(延迟)=> 触发事件

节流 throttle

场景

  • 拖拽一个元素时,要随时拿到该元素被拖拽的位置
  • 直接用drag事件,则会频繁触发,很容易导致卡顿
  • 节流:无论拖拽速度多快,都会每个100ms触发一次
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>throttle 演示</title>
        <style>
            #div1 {
                border: 1px solid #ccc;
                width: 200px;
                height: 100px;
            }
        </style>
    </head>
    <body>
        <div id="div1" draggable="true">可拖拽<div>

        <script src="./throttle.js"></script>
    </body>
</html>

const div1 = document.getElementById('div1')

// let timer = null
// div1.addEventListener('drag', function (e) {
//     if (timer) {
//         return
//     }
//     timer = setTimeout(() => {
//         console.log(e.offsetX, e.offsetY)

//         timer = null
//     }, 100)
// })

// 节流
function throttle(fn, delay = 100) {
    let timer = null

    return function () {
        if (timer) {
            return
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}

div1.addEventListener('drag', throttle(function (e) {
    console.log(e.offsetX, e.offsetY)
}))

div1.addEventListener('drag', function(event) {

})

节流:当持续触发事件时,每隔指定时间触发一次事件处理函数。当然,是你触发事件的时间小于你设置的每隔指定时间。

通俗解决:“匀速等距离”触发事件处理函数

总结:

性能优化的方案有哪些?

1.加载资源优化

2.静态资源的压缩合并    (多个js压缩合并成一个)

3.静态资源缓存             (本地缓存)

4.使用CDN让资源加载更快(比如在北京访问,会访问北京的机房。没有CDN在北京也得去访问杭州的机房)

5.使用SSR(服务端)后端渲染,数据直接输出到HTML

渲染优化

CSS放前面,JS放后面

懒加载(图片懒加载,下拉记载更多)

减少DOM查询,对DOM查询做缓存

减少DOM操作,多个操作尽量合并在一起执行(比如查询做缓存,插入也别一次性插入)

事件节流

尽早执行操作(如DOMContentLoaded)

渲染&加载

加载一个资源的过程

浏览器根据DNS服务器得到域名的IP地址

向这个IP的机器发送http请求

服务器收到、处理并返回http请求

浏览器得到返回内容

浏览器渲染页面的过程 会考

根据HTML结构生成DOM Tree

根据CSS生成CSSOM

将DOM和CSSOM整合形成RenderTree

根据RenderTree开始渲染和展示

遇到<script>时,会执行并阻塞渲染

为什么css要放在head中?

如果放在body中,要降低用户的体验,那样html会自己渲染一遍之后再去发现css再去渲染,
这样就需要考虑到电脑的配置、网络的情况以及文件内容结构的大小等情况,有可能会出现几毫秒的卡顿情况。

为什么js要放在body中?

1、 不会阻塞,让页面更快渲染出来

2、 放上面的话,会阻塞。

3、安全

问题:常见的 web 前端攻击方式有哪些?

XSS跨站请求攻击

XSRF跨域请求伪造

XSS 
再新浪微博写一篇文章,同时偷偷插入一段`<script>` 攻击代码中,获取cookie,
发送自己的服务器 发布博客,有人查看博客内容 
会把查看着者的cookie发送到攻击者的服务器 

解决: 前端替换关键字,例如替换<  为< > 为> -------前端替换会稍微影响性能。 后端替换(后端替换比较好)


安全性XSRF 跨站请求伪造 
XSFR 
你已登录一个购物网站,正在浏览商品 该网站付费接口是xxx.com/pay?id=100 但是没有任何验证 
然后你收到一封邮件,隐藏着<img src=xxx.com/pay?id=100> 你查看邮件的时候,就已经悄悄的付费购买了 

解决方法: 
增加验证流程,如输入指纹、密码、短信验证码

1. XSS

场景

1、一个博客网站,我发表一篇博客,其中嵌入<script>脚本
2、脚本内容:获取cookie,发布到我的服务器,(服务器配合跨域)
3、发布这篇博客,有人查看它,我轻松收割访问者的cookie

XSS预防

1、替换特殊字符,如<变成&lt;>变为&gt;
2、<script>变为&ltscript&gt,直接显示,而不会作为脚本执行
3、前端要替换,后端也要替换,都做总不会有错
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>xss 演示</title>
    </head>
    <body>
        <p>一段文字1</p>
        <p>一段文字2</p>
        <p>一段文字3</p>
        &lt;script&gt;alert(document.cookie);&lt;/script&gt;
    </body>
</html>

2. XSRF跨站请求伪造

场景:

 1.你正在购物,看中了某个商品,商品id是100
 2.付费接口是xxx.com/pay?id=100,但没有任何验证
 3.我是攻击者,看中了一个商品,id是200
 4.我向你发送一封电子邮件,邮件标题很吸引人
 5.但邮件正文隐藏着<img src=xxx.com/pay?id=200 />
 6.你一查看邮件就帮我购买了id是200的商品

XSRF预防

1.使用POST接口
2.增加验证,例如密码、短信验证码、指纹等

九、模块化

1.使用模块化有什么优势?

2.AMD 和CMD区别?

AMD和CommonJS的使用场景

需要异步加载JS,使用AMD

使用了npm之后建议使用CommonJS

3.构建工具有什么区别?为什么用webpack?

4.webpack的plugins有哪些,如何使用 什么意思?

5.webpack如何配置

6.webpack的各种hash有什么区别?

比如contenthash

十、JS初级面试真题

1.何为变量提升

var和let const的区别

  • var是ES5语法,let const是ES6语法,var有变量提升
  • var和let是变量,可修改;const是常量,不可修改
  • let const有块级作用域,var没有
/ // 变量提升 ES5
// console.log(a) // undefined
// var a = 200

// var a
// console.log(a) // undefined
// a = 200

// 块级作用域
for (let i = 0; i < 10; i++) {
    let j = i + 1
}
console.log(j)

typeof返回哪些类型

  • undefined string number boolean symbol
  • object(注意,typeof null === ‘object’)
  • function

列举强制类型转换和隐式类型转换

  • 强制:parseInt parseFloat toString等
  • 隐式:if、逻辑运算、==、+拼接字符串

2.手写深度比较 isEqual

手写深度比较,模拟lodash isEqual

// 判断是否是对象或数组
function isObject(obj) {
    return typeof obj === 'object' && obj !== null
}
// 全相等(深度)
function isEqual(obj1, obj2) {
    if (!isObject(obj1) || !isObject(obj2)) {
        // 值类型(注意,参与 equal 的一般不会是函数)
        return obj1 === obj2
    }
    if (obj1 === obj2) {
        return true
    }
    // 两个都是对象或数组,而且不相等
    // 1. 先取出 obj1 和 obj2 的 keys ,比较个数
    const obj1Keys = Object.keys(obj1)
    const obj2Keys = Object.keys(obj2)
    if (obj1Keys.length !== obj2Keys.length) {
        return false
    }
    // 2. 以 obj1 为基准,和 obj2 一次递归比较
    for (let key in obj1) {
        // 比较当前 key 的 val —— 递归!!!
        const res = isEqual(obj1[key], obj2[key])
        if (!res) {
            return false
        }
    }
    // 3. 全相等
    return true
}

// 测试
const obj1 = {
    a: 100,
    b: {
        x: 100,
        y: 200
    }
}
const obj2 = {
    a: 100,
    b: {
        x: 100,
        y: 200
    }
}
// console.log( obj1 === obj2 )
console.log( isEqual(obj1, obj2) )

const arr1 = [1, 2, 3]
const arr2 = [1, 2, 3, 4]

split() 和join()的区别

'1-2-3'.split('-') //[1,2,3]
[1,2,3].join('-')

数组pop push unshift shift分别做什么

// const arr = [10, 20, 30, 40]

// // pop
// const popRes = arr.pop()
// console.log(popRes, arr) 40 [10, 20, 30]

// // shift
// const shiftRes = arr.shift()
// console.log(shiftRes, arr) 10 [20, 30, 40]

// // push
// const pushRes = arr.push(50) // 返回 length
// console.log(pushRes, arr) 5 [10, 20, 30, 40, 50]

// // unshift
// const unshiftRes = arr.unshift(5) // 返回 length
// console.log(unshiftRes, arr) 5 [5, 10, 20, 30, 40]

数组的API,有哪些是纯函数

// // 纯函数:1. 不改变源数组(没有副作用);2. 返回一个数组
// const arr = [10, 20, 30, 40]

// // concat
// const arr1 = arr.concat([50, 60, 70])
// // map
// const arr2 = arr.map(num => num * 10)
// // filter
// const arr3 = arr.filter(num => num > 25)
// // slice
// const arr4 = arr.slice()

// // 非纯函数
// // push pop shift unshift
// // forEach
// // some every
// // reduce

3.你是否真的会用数组map

数组slice和splice的区别

  • 功能区别(slice - 切片,splice - 剪接)
  • 参数和返回值
  • 是否纯函数
// const arr = [10, 20, 30, 40, 50]

// // slice 纯函数
// const arr1 = arr.slice()
//console.log(arr1,arr) [10, 20, 30, 40, 50],[10, 20, 30, 40, 50]
// const arr2 = arr.slice(1, 4)
//console.log(arr2,arr) [20, 30, 40],[10, 20, 30, 40, 50]
// const arr3 = arr.slice(2)
//console.log(arr3,arr) [30, 40, 50],[10, 20, 30, 40, 50]
// const arr4 = arr.slice(-3)
// console.log(arr4,arr) [30, 40, 50],[10, 20, 30, 40, 50]

// // splice 非纯函数
// const spliceRes = arr.splice(1, 2, 'a', 'b', 'c')
//console.log(spliceRes,arr) [20, 30],[10, "a", "b", "c", 40, 50]
// const spliceRes1 = arr.splice(1, 2)
// console.log(spliceRes1, arr) [20, 30], [10, 40, 50]
// const spliceRes2 = arr.splice(1, 0, 'a', 'b', 'c')
// console.log(spliceRes2, arr) [],[10, "a", "b", "c", 20, 30, 40, 50]

[10,20,30].map(parseInt)返回结果是什(网红题)

  • map的参数和返回值
  • parseInt参数和返回值
const res = [1, 2, 3].map(parseInt)
console.log(res) //[1, NaN, NaN]
// 拆解
[1, 2, 3].map((num, index) => {
    return parseInt(num, index)
})
//parseInt(string, radix) 函数将给定的字符串以指定基数(radix/base)解析成为整数。就是 你想把string当成radix进制数解析成10进制
//radix传入0时会把1当成是10进制数,所以“1”成功了。
//radix传入1时...没有1进制数,所以不可能转换成功,返回NaN
//radix传入2时,"3"不能当作2进制数处理所以也返回NaN

ajax请求get和post的区别

  • get一般用于查询操作,post一般用户提交操作
  • get参数拼接在url上,post放在请求体内(数据体积可更大)
  • 安全性:post易于防止CSRF

4.再学闭包

函数call和apply的区别

fn.call(this,p1,p2,p3)
fn.apply(this,arguments)

事件代理(委托)是什么

image.png

闭包是什么?有什么特效?有什么负面影响

  • 回顾作用域和自由变量
  • 回顾闭包应用场景:作为参数被传入,作为返回值被返回
  • 回顾:自由变量的查找,要在函数定义的地方(而非执行的地方)
  • 影响:变量会常驻内存,得不到释放,闭包不要乱用
// // 自由变量示例 —— 内存会被释放
// let a = 0
// function fn1() {
//     let a1 = 100

//     function fn2() {
//         let a2 = 200

//         function fn3() {
//             let a3 = 300
//             return a + a1 + a2 + a3
//         }
//         fn3()
//     }
//     fn2()
// }
// fn1()

// // 闭包 函数作为返回值 —— 内存不会被释放
// function create() {
//     let a = 100
//     return function () {
//         console.log(a)
//     }
// }
// let fn = create()
// let a = 200
// fn() // 100

function print(fn) {
    let a = 200
    fn()
}
let a = 100
function fn() {
    console.log(a)
}
print(fn) // 100

5.回顾DOM操作和优化

如何阻止时间冒泡和默认行为

event.stopProppagation()
event.preventDefault()

查找、添加、删除、移动DOM节点的方法

基础中的基础,不再演示,可回顾之前的章节

如何减少DOM操作

6.jsonp本质是ajax吗

解释jsonp的原理,为何它不是真正的ajax

  • 浏览器的同源策略(服务端没有同源策略)和跨域
  • 哪些html标签能绕过跨域
  • jsonp的原理 image.png

document load和ready的区别

两等和三等的不同

  • 两等会尝试类型转换
  • 三等严格相等
  • 哪些场景才用两等?

7.是否用过Object.create()

函数声明和函数表达式的区别

  • 函数声明function fn(){…}
  • 函数表达式 const fn = function(){…}
  • 函数声明会在代码执行前预加载,而函数表达式不会
// // 函数声明
// const res = sum(10, 20)
// console.log(res) 30
// function sum(x, y) {
//     return x + y
// }

// // 函数表达式
// var res = sum(10, 20) sum is not a function
// console.log(res)
// var sum = function (x, y) {
//     return x + y
// }

new Object() 和 Object.create()的区别(网红题)

  • {}等同于new Object(),原型Object.prototype
  • Object.create(null)没有原型
  • Object.create({…})可指定原型
const obj1 = {
    a: 10,
    b: 20,
    sum() {
        return this.a + this.b
    }
}

const obj2 = new Object({
    a: 10,
    b: 20,
    sum() {
        return this.a + this.b
    }
})
//console.log(obj1===obj2) false
const obj21 = new Object(obj1)
//console.log(obj21===obj1) true
const obj3 = Object.create(null)
//console.log(obj3) 返回空对象,没有原型,没有属性
const obj4 = new Object() 
//console.log(obj4) 返回空对象{},有原型

const obj5 = Object.create({
    a: 10,
    b: 20,
    sum() {
        return this.a + this.b
    }
})//创建一个空对象,把空对象原型指向传入的对象
//console.log(obj5===obj1) false
const obj6 = Object.create(obj1)
//console.log(obj6===obj1) false
//console.log(obj6.__proto__===obj1) true
obj1.c = 1000;
console.log(obj6.c) //1000

关于this的场景题

 const User = {
 	count:1,
 	getCount:function(){
		return this.count;
	}
}
console.log(User.getCount()) //1
const func = User.getCount
console.log(func()) //undefined,this指window

8.常见的正则表达式

关于作用域和自由变量的场景题 - 1

let i;
for(i=1;i<=3;i++){
	setTimeout(function(){
		console.log(i)
	},0)
}

判断字符串以字母开头,后面字母数字下划线,长度6-30

const reg =  /^[a-zA-Z]\w{5, 29}$/
学习正则表达式的规则
手写常见的正则表达式
// 邮政编码
/\d{6}/

// 小写英文字母
/^[a-z]+$/

// 英文字母
/^[a-zA-Z]+$/

// 日期格式 2019.12.1
/^\d{4}-\d{1,2}-\d{1,2}$/

// 用户名
/^[a-zA-Z]\w{5, 17}$/

// 简单的 IP 地址匹配
/\d+\.\d+\.\d+\.\d+/

deerchao.cn/tutorials/r…

关于作用域和自由变量的场景题 - 2

let a = 100
function test(){
	alert(a) //100
	a = 10
	alert(a) //10
}
test()
alert(a) //10

9.如何获取最大值

手写字符串trim方法,保证浏览器兼容性

String.prototype.trim = function(){
	return this.replace(/^\s+/,'').replace(/\s+$/,'')
}
//原型、this、正则表达式

如何获取多个数字中的最大值

//方式一
function max(){
	const nums = Array.prototype.slice.call(arguments) //变为数组
	let max = 0
	nums.forEach(n => {
		if(n > max){
			max = n
		}
	})
	return max
}
//方式二
Math.max(10,30,20,40)
//以及Math.min

如何用JS实现继承

  • class继承
  • prototype继承

10.解析url参数

如何捕获JS程序中的异常

try{
	//todo
}catch(ex){
	console.log.error(ex) //手动捕获catch
}finally{
	//todo
}
//自动捕获
window.onerror = function(message,source,lineNum,colNum,error){
	//第一,对跨域的js,如CDN的,不会有详细的报错信息
	//第二,对于压缩的js,还要配合sourceMap反查到未压缩代码的行、列
}

什么是JSON

  • json是一种数据格式,本质是一段字符串
  • json格式和JS对象结构一致,对JS语言更友好
  • window.JSON是一个全局对象:JSON.stringify JSON.parse
    key和value必须用双引号(除非是bool或者数字)

获取当前页面url参数

  • 传统方式,查找location.search
  • 新API,URLSearchParams
// // 传统方式
// function query(name) {
//     const search = location.search.substr(1) // 类似 array.slice(1)
//     // search: 'a=10&b=20&c=30'
//     const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i')
//     const res = search.match(reg)
//     if (res === null) {
//         return null
//     }
//     return res[2]
// }
// query('d')

// URLSearchParams 浏览器兼容性问题
function query(name) {
    const search = location.search
    const p = new URLSearchParams(search)
    return p.get(name)
}
console.log( query('b') )

10.数组去重有几种方式

将url参数解析为JS对象

//传统方式,分析search
function queryToObj(){
	const res = {}
	const search = location.search.substr(1) //去掉前面的?号
	search.split('&').forEach(paramStr => {
		const arr = paramStr.split('=')
		const key = arr[0]
		const val = arr[1]
		res[key] = val
	})
	return res
}
//使用URLSearchParams
function queryToObj(){
	const res = {}
	const pList = new URLSearchParams(location.search)
	pList.forEach((val,key) => {
		res[key] = val
	})
	return res
}

手写数组flatern,考虑多层级

function flat(arr) {
    // 验证 arr 中,还有没有深层数组 [1, 2, [3, 4]]
    const isDeep = arr.some(item => item instanceof Array)
    if (!isDeep) {
        return arr // 已经是 flatern [1, 2, 3, 4]
    }

    const res = Array.prototype.concat.apply([], arr)
    return flat(res) // 递归
}

const res = flat( [1, 2, [3, 4, [10, 20, [100, 200]]], 5] )
console.log(res) //[1,2,3,4,10,20,100,200,5]

数组去重

  • 传统方式,遍历元素挨个比较、去重
  • 使用Set
  • 考虑计算效率
// // 传统方式
// function unique(arr) {
//     const res = []
//     arr.forEach(item => {
//         if (res.indexOf(item) < 0) {
//             res.push(item)
//         }
//     })
//     return res
// }

// 使用 Set (无序,不能重复)
function unique(arr) {
    const set = new Set(arr)
    return [...set]
}
const res = unique([30, 10, 20, 30, 40, 10])
console.log(res)

11.是否用过requestAnimationFrame

手写深拷贝

/**
 * 深拷贝
 * @param {Object} obj 要拷贝的对象
 */
function deepClone(obj = {}) {
    if (typeof obj !== 'object' || obj == null) {
        // obj 是 null ,或者不是对象和数组,直接返回
        return obj
    }

    // 初始化返回结果
    let result
    if (obj instanceof Array) {
        result = []
    } else {
        result = {}
    }

    for (let key in obj) {
        // 保证 key 不是原型的属性
        if (obj.hasOwnProperty(key)) {
            // 递归调用!!!
            result[key] = deepClone(obj[key])
        }
    }

    // 返回结果
    return result
}
const obj1 = {
    age: 20,
    name: 'xxx',
    address: {
        city: 'beijing'
    },
    arr: ['a', 'b', 'c']
}

const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])
//注意Object.assign不是深拷贝
const obj = {a:10,b:20,c:30}
Object.assign(obj,{d:40})
{a:10,b:20,c:30,d:40}
console.log(obj) //{a:10,b:20,c:30,d:40}

const obj1 = Object.assign({},obj,{e:50})
console.log(obj) //{a:10,b:20,c:30,d:40}
console.log(obj1) //{a:10,b:20,c:30,d:40,e:50}
obj.a = 100
console.log(obj1) //{a:10,b:20,c:30,d:40,e:50}
const obj = {a:10,{x:100,y:100}}
const obj1 = Object.assign({},obj,{c:30})
console.log(obj) //{a:10,{x:100,y:100}}
console.log(obj1) //{a:10,{x:100,y:100},c:30}
obj.a = 100
console.log(obj) //{a:100,{x:100,y:100}}
nsole.log(obj1) //{a:10,{x:100,y:100},c:30}
obj.b.x = 101
console.log(obj1.b.x) //101

介绍一下RAF requestAnimationFrame

  • 要想动画流畅,更新频率要60帧/s,即16.67ms更新一次视图
  • setTimeout要手动控制频率,而RAF浏览器会自动控制
  • 后台标签或隐藏iframe中,RAF会暂停,而setTimeout依然执行
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>JS 真题演示</title>

        <style>
            #div1 {
                width: 100px;
                height: 50px;
                background-color: red;
            }
        </style>
    </head>
    <body>
        <p>JS 真题演示</p>

        <div id="div1"></div>

        <script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.js"></script>
        <script src="./RAF.js"></script>
    </body>
</html>
// 3s 把宽度从 100px 变为 640px ,即增加 540px
// 60帧/s ,3s 180 帧 ,每次变化 3px

const $div1 = $('#div1')
let curWidth = 100
const maxWidth = 640

// // setTimeout
// function animate() {
//     curWidth = curWidth + 3
//     $div1.css('width', curWidth)
//     if (curWidth < maxWidth) {
//         setTimeout(animate, 16.7) // 自己控制时间
//     }
// }
// animate()

// RAF
function animate() {
    curWidth = curWidth + 3
    $div1.css('width', curWidth)
    if (curWidth < maxWidth) {
        window.requestAnimationFrame(animate) // 时间不用自己控制
    }
}
animate()

前端性能如何优化?一般从哪几个方面考虑?

  • 原则:多使用内存、缓存,减少计算、减少网络请求
  • 方向:加载页面,页面渲染,页面操作流畅度