JavaScript核心知识点(上篇)

看了很多 JS 的书籍和教程,往往会模糊焦点,这篇文章会做一个梳理,将 JS 中的重难点进行总结。

文章分为上下两篇。上篇是基本知识,下篇是进阶知识。


#变量类型和计算

引例:

  1. 值类型和引用类型的区别

  2. typeof能判断哪些类型?

  3. 何时使用 === 何时使用 ==

  4. 手写深拷贝

变量类型

  • 值类型
  • 引用类型

值类型

let a = 100
let b = a
a = 200
console.log(b) // 100

引用类型

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

分析:

值类型的变量,赋值时会重新将值拷贝一份,因此两个变量之间相互独立。

引用类型的变量名指向的是一个内存地址,真实的对象存储在该内存地址所指向的内存中(堆)。当变量进行赋值时let b = ab 也会指向 a 所指向的那块内存,两个变量实际上指向的是同一个对象。

常见值类型

const s = 'abc' // 字符串
const n = 100 // 数字
const b = true // 布尔
const s = Symbol('s') // Symbol

常见引用类型

const obj = { x: 100 } // 对象
const arr = ['a', 'b', 'c'] // 数组
const n = null // 空,特殊引用类型,指针指向为空地址
function fn() { } // 函数,特殊引用类型,但不用于存储数据,所以没有“拷贝,复制函数”这一说

typeof 运算符

  • 识别所有值类型
  • 识别函数
  • 判断是否是引用类型(不可再细分)

识别所有值类型

let a                       typeof a // undefined
const s = 'abc'             typeof a // string
const n = 100               typeof n // number
const b = true              typeof b // boolean
const s = Symbol('s')       typeof s // symbol

识别函数

typeof console.log // function
typeof function () { } // function

判断是否是引用类型(不可再细分)

typeof null // object
typeof ['a', 'b'] // object
typeof { x: 100 } // object

深拷贝

/**
 * 深拷贝
 */
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])

/**
 * 深拷贝
 * @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
}

变量计算

类型转换:

  • 字符串拼接
  • ==
  • if语句和逻辑运算

字符串拼接

const a = 100 + 10 // 110
const b = 100 + '10' // '10010'
const c = true + '10' // 'true10'

==

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) { }

if语句和逻辑运算

  • truly变量:!!a === true 的变量

  • falsely变量:!!a === false 的变量

truly变量

!!1 // true
!!{} // true

falsely变量

!!0 // false
!!null // false
!!'' // false
!!undefined // false
!!NaN // false
!!false // false

例子1:if 语句

// truly变量
const a = true
if (a) {
    // ...
}
const b = 100
if (b) {
    // ...
}

// falsely变量
const c = ''
if (c) {
    // ...
}

const d = null
if (d) {
    // ...
}

let e
if (e) {
    // ...
}

例子2:逻辑判断

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

#原型和原型链

引例:

  1. 如何判断一个变量是不是数组?
  2. 手写一个简易的jQuery,考虑插件和扩展性。
  3. class的原型本质,怎么理解?

知识点:

  1. class和继承
  2. 类型判断instanceof
  3. 原型和原型链

class

  • constructor
  • 属性
  • 方法

例子:

// 类
class Student {
    constructor(name, number) {
        this.name = name
        this.number = number
        // this.gender = 'male'
    }
    sayHi() {
        console.log(`姓名 ${this.name} ,学号 ${this.number}`)
        // console.log(
        //     '姓名 ' + this.name + ' ,学号 ' + this.number
        // )
    }
    // study() {
    // }
}

// 通过类 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()

类型判断 instanceof

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

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

原型

// class 实际上是函数,可见是语法糖
typeof People // 'function'
typeof Student // 'function'

// 隐式原型和显式原型
console.log(xialuo.__proto__) // 隐式原型
console.log(Student.prototype) // 显式原型
console.log(xialuo.__proto__ === Student.prototype) // true

原型关系

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

示意图:

原型

基于原型的执行规则

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

原型链

console.log(Student.prototype.__proto__)
console.log(People.prototype)
console.log(People.prototype === Student.prototype.__proto__) // true

示意图

原型链
原型链

引例解答:

如何判断一个变量是不是数组?

a instanceof Array

手写一个简易的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'))

class的原型本质,怎么理解?

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

#作用域和闭包

引例:

  • this的不同应用场景,如何取值?
  • 手写bind函数
  • 实际开发中闭包的应用场景,举例说明
  • 创建10个a标签,点击的时候弹出对应的序号

知识点:

  • 作用域和自由变量
  • 闭包
  • this

作用域

作用域

分类

  • 全局作用域
  • 函数作用域
  • 块级作用域(ES6新增)
// ES6 块级作用域
if (true) {
    let x = 100 // 只有使用let或const等ES6特性才会触发块级作用域
}
console.log(x) // 会报错

自由变量

  • 一个变量在当前作用域没有定义,但被使用了
  • 向上级作用,一层一层依次查找,直到找到为止

闭包

  • 作用域应用的特殊情况,有两种表现:
  • 若函数作为参数被传递
  • 函数作为返回值被返回

函数作为参数

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

函数作为返回值

// 函数作为返回值
function create() {
    const a = 100
    return function () {
        console.log(a)
    }
}

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

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

this

  1. 作为普通函数
  2. 使用call、apply、bind
  3. 作为对象方法被调用
  4. 在class方法中调用
  5. 箭头函数

this 取什么值,是在函数执行的时候确定的,不是在函数定义的时候确定的。该原则适用于以上5种情况。

例子1:普通函数及call、apply、bind中的this

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

fn1() // window

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

const fn2 = fn1.bind({ x: 200 })
fn2() // { x : 200 }

注:关于call,apply,bind的区别,见 JavaScript 中 call()、apply()、bind() 的用法

例子2:对象方法中的this

const zhangsan = {
    name: '张三',
    sayHi() {
        // this 即当前对象
        console.log(this)
    },
    wait() {
        setTimeout(function () {
            // this === window
            console.log(this)
        })
    }
}

例子3:箭头函数中的this

const lisi = {
    name: '李四',
    sayHi() {
        // this 即当前对象
        console.log(this)
    },
    wait() {
        setTimeout(() => {
            // this === window
            console.log(this)
        })
    }
}

例子4:class中的this

class People {
    constructor(name) {
        this.name = name
        this.age = 20
    }
    sayHi() {
        console.log(this)
    }
}
const zhangsan = new People('张三')
zhangsan.sayHi() // zhangsan 对象

引例解答:

1. this的不同应用场景,如何取值?

  • 作为普通函数
  • 使用call、apply、bind
    1. 作为对象方法被调用
  • 在class方法中调用
    1. 箭头函数

2. 手写bind函数

// 模拟 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)
    }
}

/* 
fn1.__proto__ === Function.prototype // true
*/

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工具
// 闭包隐藏数据,只提供 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标签,点击的时候弹出对应的序号
let 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) // i 是全局作用域
    })
    document.body.appendChild(a)
}

正确示例:✅

// 创建10个a标签,点击的时候弹出对应的序号
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) // i 是块级作用域
    })
    document.body.appendChild(a)
}

注:若将上面正确示例中for循环的let i = 0改为var i = 10,则其结果同错误示例,因为var定义的变量为全局变量。


#异步

引例:

  1. 同步和异步的区别是什么?
  2. 手写用promise加载一张图片
  3. 前端使用异步的场景有哪些?
  4. 读下段代码:
console.log(1)
setTimeout(function () {
    console.log(2)
}, 1000)
console.log(3)
setTimeout(function () {
    console.log(4)
}, 0)
console.log(5)

问:思考,上段代码中,数字的打印顺序是什么?本节末尾会解答。

知识点:

  • 单线程和异步
  • 应用场景
  • callback hell 和 Promise

单线程和异步

  • JS是单线程语言,只能同时做一件事
  • 浏览器和notejs已支持JS启动进程,如Web Worker
  • JS和DOM渲染共用同一个线程,因为JS可修改DOM结构
  • 遇到等待(网络请求,定时任务)不能卡住
  • 需要异步
  • 回调callback函数形式

异步和同步

// 异步 100 300 200
console.log(100)
setTimeout(function () {
    console.log(200)
}, 1000)
console.log(300)

// 同步 100 200 300
console.log(100)
alert(200)
console.log(300)

js是单线程语言,异步不会阻塞代码执行,同步会阻塞代码执行。

应用场景

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

ajax:


// start end data1
console.log('start')
$.get('./data1.json', function (data1) {
    console.log(data1)
})
console.log('end')

图片加载:

// start end loaded
console.log('start')
let img = document.createElement('img')
img.onload = function () {
    console.log('loaded')
}
img.src = '/xxx.jpg'
console.log('end')

定时器:

// 100 300 200
console.log(100)
setTimeout(function () {
    console.log(200)
}, 1000)
console.log(300)

/*-----------------------*/

// 100 300 200 200 200 ...
console.log(100)
setInterval(function () {
    console.log(200)
}, 1000)
console.log(300)

callback hell(回调地狱)

$.get(url1, (data1) => {
    console.log(data1)

    $.get(url2, (data2) => {
        console.log(data2)

        $.get(url3, (data3) => {
            console.log(data3)
        })
    })
})

Promise

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))

引例解答:

1. 同步和异步的区别是什么?

异步不会阻塞代码执行,同步会阻塞代码执行。

2. 手写用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 url1 = '1.jpg'
const url2 = '2.jpg'

loadImg(url1).then(img1 => {
    console.log(img1.width)
    return img1 // then返回普通对象,则立即执行下一个then
}).then(img1 => {
    console.log(img1.height)
    return loadImg(url2) // then返回新的promise实例,
                         // 则下一个then会在这个新的promise状态改变后执行
}).then(img2 => {
    console.log(img2.width)
    return img2
}).then(img2 => {
    console.log(img2.height)
}).catch(ex => console.error(ex))

注:then 中如果返回新的promise,那么下一级then会在新的promise状态改变后执行;如果返回其他任何值,则会立即执行下一级then

3. 前端使用异步的场景有哪些?

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

4. 读下段代码

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


#DOM

引例:

  1. DOM是哪种数据结构?
  2. DOM操作的常用API
  3. attr和property的区别
  4. 一次性插入多个DOM节点,考虑性能

知识点:

  • DOM本质
  • DOM节点操作
  • DOM结构操作
  • DOM性能

DOM本质

DOM的本质是一棵树,是从HTML文件解析出来的一棵树。

DOM节点操作

  • 获取DOM节点
  • poperty
  • attribute

获取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])

const pList = document.querySelectorAll('p')
console.log('pList', pList)

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

poperty

// property 形式
p1.style.width = '100px'
console.log(p1.style.width)
p1.className = 'red'
console.log(p1.className)
console.log(p1.nodeName)
console.log(p1.nodeType) // 1

attribute

// attribute
p1.setAttribute('data-name', 'imooc')
console.log(p1.getAttribute('data-name'))
p1.setAttribute('style', 'font-size: 50px;')
console.log(p1.getAttribute('style'))

property和attribute的异同:property会修改对象的属性,但不会体现到html结构中;attribute会修改html属性,并改变html结构。两者都有可能引起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)
const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => {
    if (child.nodeType === 1) {
        return true
    }
    return false
})
console.log('div1ChildNodesP', div1ChildNodesP)

注:node节点的childNodes属性会返回包括 '#text' 节点在内的所有直接子节点。

删除子元素

div1.removeChild(div1ChildNodesP[0])

DOM性能

  • DOM操作非常昂贵,避免频繁的DOM操作
  • 对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)

引例解答:

1. DOM是哪种数据结构?

答:树(DOM树)

2. DOM操作的常用API

答:DOM节点操作,DOM结构操作,attribute和property

3. attr和property的区别

答:property会修改对象的属性,但不会体现到html结构中;attribute会修改html属性,并改变html结构。两者都有可能引起DOM重新渲染。

4. 一次性插入多个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)

#BOM

引例:

  1. 如何识别浏览器的类型?
  2. 分析拆解url各个部分

知识点:

  • navigator
  • screen
  • location
  • history

navigator和screen

// navigator
const ua = navigator.userAgent
const isChrome = ua.indexOf('Chrome')
console.log(isChrome)

// screen
console.log(screen.width)
console.log(screen.height)

location和history

// location
console.log(location.href)
console.log(location.protocol)
console.log(location.pathname)
console.log(location.search)
console.log(location.hash)

// history
history.back()
history.forward

引例解答:见上。


#事件

引例:

  1. 编写一个通用的事件监听函数
  2. 描述事件冒泡的流程
  3. 无限下拉的图片列表,如何监听每个图片的点击?

知识点:

  • 事件绑定
  • 事件冒泡
  • 事件代理

事件绑定

const btn = document.getElementById('btn1')
btn.addEventListener('click', event => {
    console.log('clicked')
})

// 通用的绑定函数
function bindEvent(elem, type, fn) {
    elem.addEventListener(type, fn)
}

const a = document.getElementById('link1')
bindEvent(a, 'click', e => {
    e.preventDefault() // 阻止默认行为
    alert('clicked')
})

事件冒泡

<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('取消')
})

事件代理

  • 代码简介
  • 减少浏览器内存使用
  • 但是,不要滥用
<div id="div1">
    <a href="#">a1</a>
    <a href="#">a2</a>
    <a href="#">a3</a>
    <a href="#">a4</a>
</div>
<button>
    点击增加一个 a 标签
</button>
const div1 = document.getElementById('div1')
div1.addEventListener('click', e => {
    const target = e.target
    if (e.nodeName === 'A') {
        alert(target.innerHTML)
    }
})

通用事件绑定

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)
})

引例解答:

1. 编写一个通用的事件监听函数

见上 “通用事件绑定”

2. 描述事件冒泡的流程

  • 基于DOM树形结构
  • 事件会顺着触发元素往上冒泡
  • 应用场景:代理

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

  • 事件代理
  • 用e.target获取触发元素
  • 用matches来判断是否是触发元素

#Ajax

引例:

  1. 手写一个简易的ajax
  2. 跨域的常用实现方式

知识点:

  • XMLHttpRequest
  • 状态码
  • 跨域:同源策略、跨域解决方案

XMLHttpRequest

// GET
const xhr = new XMLHttpRequest()
xhr.open('GET', '/api', true) // true表示异步
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            alert(xhr.responseText)
        }
    }
}
xhr.send(null)

/*----------------*/
// POST
const xhr = new XMLHttpRequest()
xhr.open('POST', '/login', true) // true表示异步
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            alert(xhr.responseText)
        }
    }
}

const postData = {
    username: 'zhangsan',
    password: '123'
}
xhr.send(JSON.stringify(postData))

状态码

xhr.readystatechange

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

xhr.status

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

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

  • 什么是跨域(同源策略)
  • JSONP
  • CORS(服务端支持)

同源策略

  • ajax请求时,浏览器要求当前网页和server必须同源(安全)
  • 同源:协议、域名、端口,三者必须一致。
  • 如前端:http://a.com:8080 ; server:https://b.com/api/xxx

加载图片,css js可无视同源策略

  • <img rc=跨域的图片地址 />
  • <link href=跨域的css地址 />
  • <script src=跨域的js地址></script>
  • <img />可用于统计打点,可使用第三方统计服务
  • <link /> <script> 可使用CDN,CDNn一般都是外域
  • <script>可实现JSONP

跨域

  • 所有的跨域都必须经过server端允许和配合
  • 未经server端允许就实现跨域,说明浏览器有漏洞,危险信号

JSONP

  • 访问https://baidu.com,服务端一定返回一个html文件吗?
  • 服务器可以任意动态拼接数据返回,只要符合html格式要求
  • 同理 <script src="http://baidu.com/getData.js">
  • <script>可绕过跨域限制
  • 服务器可以任意动态拼接数据返回
  • 所以<script>就可以获得跨域的数据,只要服务端愿意返回
<script>
window.callback = function(data){
    this.console.log(data)
}
</script>
<script src="http://www.baidu.com/getData.js"></script>
<!--将返回 callback({x:100, y:200})-->

jQuery实现jsonp

$.ajax({
    url: 'http://baicu.com/x-origin.json',
    dataType: 'jsonp'
    jsonpCallback: 'callback',
    success: function (data) {
        console.log(data)
    }
})

CORS - 服务器设置http header

// 第2个参数填写允许跨域的域名,称不建议直接写 '*'
response.setHeader('Access-Control-Allow-Origin', 'http://baidu.com');
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');

引例解答:

1. 手写一个简易的ajax

// 常规形式
function ajax(url, successFn) {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url, true) // true表示异步
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                successFn(xhr.responseText)
            }
        }
    }
    xhr.send(null)
}

// Promise 形式
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. 跨域的常用实现方式

  • JSONP
  • CORS

#存储

引例:

  • 描述cookie、localStorage、sessionStorage的区别

知识点

  • cookie
  • localStorage和sessionStorage

cookie

  • 本身用于浏览器和server通讯
  • 被“借用”到本地存储来
  • 可用document.cookie= '...'来修改

Cookie的缺点

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

localStorage和sessionStorage

  • HTML5专门为存储而设计,最大可存5M
  • API简单易用,setItem, getItem
  • 不会随着HTTP请求被发送出去。
  • LocalStorage数据会永久存储,除非代码或手动删除
  • sessionStorage数据只存在于当前会话,浏览器关闭则清空
  • 一般用localStorage会更多一些

引例解答:

描述cookie、localStorage、sessionStorage的区别

  • 容量
  • API易用性
  • 是否跟随http请求发送出去

#开发环境

知识点:

  • git
  • 调试工具
  • 抓包
  • webpack
  • babel
  • Linux常用命令

git

  • 最常用的代码版本管理工具
  • 大型项目需要多人协作开发,必须服用git
  • Mac OS自带git命令,windows可去官网下载安装
  • git服务端常见的github coding.net等
  • 大公司会搭建自己的内网git服务

常用git命令

git add .
git checkout xxx
git commit -m "xxx"
git push origin master
git pull origin master
git diff/git diff filename
git log
git show commit_id
git branch
git checkout -b xxx/git checkout xxx
git fetch
git merge xxx
git stash
git stash pop

常用 Git 命令清单

chrome调试工具

  • elements
  • console
  • debugger
  • network
  • application

抓包

  • 移动端h5页面,查看网络请求,需要用装包工具

  • windows一般用发到fiddler

  • Mac OS一般用charles

  • 手机和电脑连同一个局域网

  • 将手机代理到电脑上

  • 手机浏览网页即可找抓包

  • 查看网络请求

  • 网址代理

  • https

Charles手机代理设置

webpack 和babel

体量太大,此处略。

Linux命令

  • 公司的线上机器一般都是linux(参考阿里云)
  • 测试机也需要保持一致,用Linux
  • 测试机或者线上机出了问题,本地又不能复现,需要去排查

#运行环境

  • 运行环境即浏览器(server端有nodejs)
  • 下载网页代码渲染出页面,期间会执行若干js
  • 要保证代码在浏览器中:稳定且高效

知识点

  • 网页加载过程
  • 性能优化
  • 安全

网页加载过程

引例:

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

  2. window.onload和DOMContentLoaded的区别

加载资源的形式

  • html代码
  • 媒体文件,如图片视频等
  • Javascript css

加载资源的过程

  • DNS解析:域名 -> ip地址
  • 浏览器根据IP地址向服务器发起HTTP请求
  • 服务器处理HTTP请求,并返回给浏览器

渲染页面的过程

  • 根据html代码生成DOM Tree
  • 根据css代码生成CSSOM
  • 将DOM Tree和 CSSOM 整合形成Render Tree
  • 根据Render Tree渲染页面
  • 遇到<script>则暂停渲染,优先加载并执行js代码,完成再继续

window.onload和DOMContentLoaded

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

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

引例解答:

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

  • 下载资源:各个资源类型,下载过程
  • 渲染页面:结合HTML css JavaScript 图片等

2. window.onload和DOMContentLoaded的区别

  • window.onload: 页面的全部资源加载完成才会执行,包括图片视频
  • DOMContentLoaded:DOM渲染完即可执行,此时图片视频还可能没有加载完

性能优化

  • 是一个综合性问题,没有标准答案,要求尽量全面
  • 某些细节问题,可能会单独提问:手写防抖、节流

性能优化原则

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

从何入手

  • 让加载更快
    • 减少资源体积:压缩代码
    • 减少请求访问次数:合并代码,SSR服务器端渲染,缓存
    • 使用更快的网络:CDN
  • 让渲染更快
    • css放在head, js放在body最下面
    • 尽早开始执行js,用DOMContentLoaded触发
    • 懒加载(图片懒加载,上滑加载更多)
    • 对DOM查询进行缓存
    • 频繁DOM操作,合并到一起插入DOM结构
    • 节流 throttle 防抖 debounce

防抖 debounce

  • 监听一个输入框的文字变化后触发change事件
  • 直接用keyup事件则会频繁触发change事件
  • 防抖:用户输入结束或暂停时才会触发change事件
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事件,则会频繁触发,很容易导致卡顿
  • 节流:无论拖拽速度多快,都会每隔100毫秒触发一次
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) {

})

安全

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

  • XSS跨站请求攻击

  • XSRF跨站请求伪造

XSS跨站请求攻击

比如在一个博客网站上发表文章,其中嵌入script脚本。脚本内容是获取cookie,发送到我的服务器(服务器配合跨域)。若有人浏览我的文章,我就可以轻松收割访问者的cookie。

XSS预防:替换特殊字符,如<变为&lt;>变为&gt;,则<script>变为&lt;script&gt;,直接显示,而不会作为脚本执行。前端要替换,后端也要替换,都做总不会有错。

开源库 xss

XSRF跨站请求伪造

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

XSRF预防:使用post接口;增加验证,例如密码、短信验证码、指纹等。

参考资料:

初级JavaScript面试

JavaScript原型继承

原型继承

Javascript继承机制的设计思想

promise

常用 Git 命令清单

Charles手机代理设置

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容

  • JavaScript语言精粹 前言 约定:=> 表示参考相关文章或书籍; JS是JavaScript的缩写。 本书...
    微笑的AK47阅读 580评论 0 3
  • JS基础 页面由三部分组成:html:超文本标记语言,负责页面结构css:层叠样式表,负责页面样式js:轻量级的脚...
    小贤笔记阅读 599评论 0 5
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,180评论 0 3
  • 第一章 错误处理: 错误: 程序运行过程中,导致程序无法正常执行的现象(即bug) 现象: 程序一旦出错,默认会报...
    fastwe阅读 1,112评论 0 1
  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,556评论 0 5