前端JavaScript知识点整理

220 阅读9分钟

导言


本篇文章会对JavaScript基础知识加面试题有个概览性的整理。

前言

我们看下下面几个题目,思考下是否可以立马回答出来。

  • typeof能判断哪些类型?
  • 何时使用===何时使用==?
  • window.onload和DOMContentLoaded的区别?
  • JS创建10个<a>标签,点击的时候弹出对应的序号
  • 手写节流throttle、防抖debounce
  • Promise解决了什么问题?

拿到一个面试题,你第一时间看到的是什么?
然后如何待网上搜出来的永远做不完的题海?
如何对待接下来遇到的面试题?

如何搞定所有面试题

  • 拿到一个面试题,第一时间看到 -> 考点
  • 如何看待做不完的题海 -> 不变应万变(题目可变,考点不变)
  • 如何对待接下来的题目 -> 题目到知识点,再到题目

前端知识体系

  • 什么是知识体系?
    高效学习三部曲:找准知识体系;可以训练;及时反馈。 知识体系:结构化的知识范围。 涵盖所以只是点;结构化,有组织,易扩展。
  • 从哪些方面梳理?
    W3C标准:html、css、DOM操作、BOM操作、AJAX、事件绑定。应用的标准。
    ECMA 262标准:规范了js或者es的语言的语法,比如es6,变量怎么定义,函数怎么定义以及原型、闭包、promise。语法的标准。
    开发环境
    运行环境
  • 知识体系
    JS基础语法
    JS-Web-API
    开发环境
    运行环境

JS基础-变量类型和计算

知识点

变量类型

  • 值类型和引用类型
// 值类型
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

堆从下往上排列。a和b存储的都是内存地址,指向对象{age: 20}

  • 常见的值类型
let a // undefined
const s = 'abc'
const n = 100
const b = true
const s = Symbol('s')

注意,const定义常量一定要复制,否则会报错。

  • 常见的引用类型
const obj = { x: 100}
const arr = ['a','b','c']
const n = null // 特殊引用类型,指针指向为空地址
function fn(){} // 特殊引用类型,但不用于存储数据,所以没有“拷贝、复制函数”一说
  • typeof运算符
  1. 识别所有的值类型
  2. 识别函数
  3. 判断是否是引用类型(不可再细分)
// 判断所有的值类型
let a ;                 typefof a // 'undefined'
const s = 'abc';        typeof s // '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'
  • 深拷贝 如果希望在本地代理一个server来调试代码,可以按照下面的方法和步骤。
  1. 确保本地安装了node和npm
  2. 全局安装http-server模块npm install http-server -g
  3. terminal里启动server并且设置一个端口http-server -p 8001
  4. 本地创建文件夹deepclone,并且创建index.html和deepclone.js文件。
  5. index.html 文件
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
		<meta http-equiv="X-UA-Compatible" content="ie=edge">
		<title>Deepclone</title>
	</head>
	<body>
		<p>一段文字</p>
		<script type="text/javascript" src="./deepclone.js"></script>
	</body>
</html>

6)deepclone.js文件

/**
*浅拷贝
*/
const obj1 = {
	age: 20,
	name: 'erina',
	address: {
		city: 'beijing'
	},
	arr: ['a','b','c']
}

const obj2 = obj1

obj2.address.city = 'shanghai'

console.log(obj1.address.city) // shanghai

/**
*深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj = {}) {
	// 如果obj不是对象,数组,或者为null,直接返回,不需要做深拷贝
	if(typeof obj !== 'object'|| 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 obj3 = deepClone(obj1);
obj3.address.city = 'chendu'
console.log(obj3, obj1)

结果如下图所示。

  • 变量计算-类型转换
  1. 字符串拼接
const a = 100 + 10 // 110
const b = 100 + '10' // '10010'
const c = true + '10' // 'true10'
  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){}
// 因为这里的a可能为null,可能为undefined,与null == 都能成立
  1. if语句和逻辑运算
    truly变量:!!a === true 的变量
    falsely变量:!!aa === false 的变量
// 以下是falsely变量。除此之前都是truly变量
!!0 === flase
!!NaN === false
!!'' === false
!!null === false
!!undefined === false
!!false === false

// if语句
// truly 变量
const a = true
if (a) {
    // ....
}
// falsely 变量
const c = ''
if (c) {
    // ....
}

// 逻辑判断(短路运算)
console.log(10 && 0) // 0
console.log('' || 'abc') // 'abc'
console.log(!window.abc) // true
  • 问题整理
  1. typeof能判断哪些类型?
  2. 何时使用===何时使用==?
  3. 值类型和引用类型的区别
const obj1 = { x: 100, y: 200 }
const obj2 = obj1;
let x1 = obj1.x
obj2.x = 101
x1 = 102
console.log(obj1) // {x: 101}
  1. 手写深拷贝
    注意判断值类型和引用类型;
    注意判读是数组还是对象;
    递归;

JS基础-原型和原型链

知识点和题目

题目

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

知识点

  • class和继承
  • 类型判断Instanceof
  • 原型和原型链

class和继承

class

  • constructor
  • 属性
  • 方法
// 类
class Student {
	constructor(name, number) {
		this.name = name;
		this.number = number;
	}

	sayHi() {
		console.log(`姓名${this.name},学号${this.number}`)
	}
}
// 通过类new对象/实例
const xialuo = new Student('夏洛', 100);
console.log(xialuo)  // {name: "夏洛", number: 100}
const madongmei = new Student('马冬梅', 100);
console.log(madongmei) // {name: "马冬梅", number: 100}

继承

当我们有很多个class,这些class中有一些公共属性的时候,可以抽离出来,后面用来继承。

  • extends 继承通过extends来做的
  • super 执行父类的构造函数,也就父类的构建过程
  • 扩展和重写方法
// 父类
class People {
	constructor(name) {
		this.name = name;
	}

	eat() {
		console.log(`${this.name} eat something`)
	}
}

// 子类 1
class Student extends People {
	constructor(name, number) {
		super(name)
		this.number = number
	}

	sayHi() {
		console.log(`姓名${this.name},学号${this.number}`)
	}

}

// 子类2
class Teacher extends People {
	constructor(name, major) {
		super(name)		
		this.major = major;
	}

	teach() {
		console.log(`${this.name} 教授 ${this.major}`)
	}
}

// 通过类new对象/实例
const xialuo = new Student('夏洛', 100);
console.log(xialuo) // {name: "夏洛", number: 100}
xialuo.sayHi(); // 姓名夏洛,学号100
xialuo.eat(); // 夏洛 eat something
const teacherWang = new Teacher('王老师', '语文'); 
console.log(teacherWang) // {name: "王老师", major: "语文"}
teacherWang.teach(); // 王老师 教授 语文
teacherWang.eat(); // 王老师 eat something

原型

类型判断-instanceof

// 可以通过instanceof判断变量属于哪个class,属于哪个构造函数
xialuo instanceof Student // true
xialuo instanceof Pepole // true Pepole是xialuo的父类
xialuo instanceof Object // true Object是所有class的父类
[] instanceof Array // true 
[] instanceof Object // true 可以理解为Array也是一个类,Object是Array的父类

{} 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(放这个class的方法)
  • 每个实例都有隐式原型__proto__
  • 实例的__proto__指向对应的class的prototype
  • 基于原型的执行规则:获取属性xialuo.name或执行方法xialuo.sayhi()时,现在自身属性和方法寻找,如果找不到则自动去_proto_中查找。

原型链和instanceof

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

注意:根据图中我们看到,name是xiaoluo本身的一个属性,方法sayHi不是xiaoluo本身的一个属性,下面我们来验证。

xialuo.name // "夏洛"
xialuo.hasOwnProperty('name') // true
xialuo.hasOwnProperty('sayHi') // false
xialuo.sayHi() // 姓名 夏洛 学号 100 可以执行,但不是xialuo的属性方法
xialuo.hasOwnProperty('hasOwnProperty') // false hasOwnProperty也不是夏洛的属性方法,但是可以执行

那么hasOwnProperty从哪里来的呢?

People.prototype的__proto__指向Object.prototype。Object.prototype 具有toString、hasOwnProperty等方法。

instanceof原理:变量能不能顺着其隐式原型找到相对应的class(显式原型),能找到就成立,找不到就不成立。 例如:

xialuo instanceof Array // false
  • class是ES6语法规范,由ECMA委员会发布
  • ECMA只规定语法规则,即我们代码的书写规范,不规定如何实现。
  • 以上实现方式都是V8引擎的实现方式,也是主流的实现方式。

问题解答和总结

  • 如何准确判断一个变量是不是数组? a instanceof Array
  • 手写一个简易的jQuery,考虑插件和扩展性 index.html文件
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
		<meta http-equiv="X-UA-Compatible" content="ie=edge">
		<title>jquery demo演示</title>
	</head>
	<body>
		<p>一段文字 1</p>
		<p>一段文字 2</p>
		<p>一段文字 3</p>
		<script type="text/javascript" src="./jquery-demo.js"></script>
	</body>
</html>

jquery-demo.js文件

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插件的扩展性

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

jQuery.prototype.dialog = function (info) {
	alert(info)
}
// ’造轮子‘
class myJQuery extends jQuery {
	constructor(selector) {
	    super(selector)
	}
	//扩展自己的方法
	addClass(className) {

	}
	style(data) { }
}
  • class的原型本质,怎么理解?

JS基础-作用域和闭包

作用域和自由变量

知识点

  • 作用域和自由变量
    作用域:a的作用域全局,a1的作用域在fn1函数作用域范围内,a2的作用域在fn2的函数作用域范围内,a3的作用域在fn3的函数作用域范围内。
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();

作用域分为:全局作用域、函数作用域、块级作用域(ES6新增)

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

自由变量:一个变量在当前作用域没有定义,但被使用了。向上级作用域一层一层一次寻找,知道找到为止。如果到全局作用域的没有找到,则报错XX is not defined。

闭包

作用域应用的特殊情况,有俩种表现:1)函数作为参数被传递;2)函数作为返回值被返回。
闭包作用域:自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方。 其实不仅仅针对闭包作用域,所有的都是,所有的自由变量的查找,都是在函数定义的地方。

// 函数作为返回值
function create() {
    const a = 100
    return function () {
        console.log(a)
    }
}
// 返回的是function,此时还未被执行
const fn = create()
const a = 200
// function 被执行
fn() // 100
// 函数作为参数被传递
function print(fn) {
    let a = 200
    fn()
}
let a = 100
function fn() {
    console.log(a)
}
print(fn); // 100

this

  • this的使用场景
    作为普通函数:直接返回window
    使用call apply bind:传入什么绑定什么
    作为对象方法被调用:返回对象本身
    在class方法中调用:当前实例本身
    箭头函数:寻找上级this的作用域
    this作用域:this取什么值是在函数执行的时候决定的,不是在执行的时候决定的。
function fn1() {
    console.log(this)
}
fn1() // window
fn1.call({x: 100}) // {x: 100}

const fn2 = fn1.bind({x:200}) // bind需要返回一个新的函数
fn2() // {x:200}
const zhangsan = {
	name: '张三',
	sayHi() {
		// this即当前对象
		console.log(this)
	},
	wait() {
	    console.log(this) // 即当前对象
		setTimeout(function() {
			// this === wondow
			console.log(this)
		})
	}
}

这里的setTimeout被当做一个普通函数调用的,setTimeout本身函数执行。并不是被当做对象zhangsan的某个方法sayHi或者wait执行的,所以this对象是window。

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

箭头函数的作用域:永远取它上级作用域的this的值。

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

问题解答

  • this的不同应用场景,如何取值?
  • 手写bind函数
    bind的应用
function fn1(a, b, c) {
	console.log('this',this)
	console.log(a, b, c)
	return 'this is fn1'
}

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

bind不是fn1的属性,那么往原型链上层找,bind、call和apply都是Function的属性。 手写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)

返回结果和上面用原生bind的结果一致。

  • 实际开发中的闭包应用场景,举例说明 例子: 闭包隐藏数据,做一个简单的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')) // 100

data是在creatCache的函数作用域内的,它不会被外界所访问到。不通过set,get是没法修改和获取data的值的。
直接访问data.b 是会报错的。

  • 创建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)
	})
}
document.body.appendChild(a);

上面点击对应的不管哪个值,弹出的都是10,因为alert(i)是在click点击的时候才会发生,这个时候i已经变成10了。 如何解决呢?在for里面定义let i,就可以解决。let是块级作用域,每次for循环的时候都会创建一个块级作用域。 上面不一样,上面的i是个全局的作用域。

// 创建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)
	})
}
document.body.appendChild(a);

JS基础-异步

同步和异步的区别

单线程和异步

  • JS是单线程语言,只能同时做一件事
  • 浏览器和nodejs已支持JS启动进程,如Web Worker
  • JS和DOM渲染共用同一个线程,因为JS可修改DOM结构。(执行JS,DOM渲染得停止。渲染DOM结构,JS运行得停止)
  • 遇到等待(网络请求,定时任务)不能卡主。(异步就是由单线程的背景提出来的,因为单线程的缺陷,才要支持异步。
  • 需要异步(异步解决单线程等待问题)
  • 异步基于后调callback函数形式实现的

异步和同步

  • 基于JS是单线程语言
  • 异步不会阻塞代码执行
  • 同步会阻塞代码执行
// 异步
console.log(100)
setTimeout(function () {
    console.log(200)
}, 1000)
console.log(300)

先打印100,然后打印300,1s后打印200。 异步通过callback回调函数执行,异步特点不会阻塞后面代码的执行。
回调函数: function(){console.log(200)}

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

先打印出100,再弹出200,如果不点击确定,300不会打印,这个地方会堵塞。后面代码不会执行。

  • 同步和异步的区别是什么?
  • 手写用Promise加载一张图片
  • 前端使用异步的场景有哪些?
    1)网络请求,如ajax图片加载
    2)定时任务,如setTimeout
// ajax 
console.log('start')
$.get('./data1.json', function(data1) {
    console.log(data1)
})
console.log('end')

先打印start,然后ajax去请求data1.json,然后打印end。等ajax请求返回后,打印data1。

// 图片加载
console.log('start')
let img = document.createElement('img')
img.onload = function () {
    console.log('loaded')
}
img.src = '/xxx.png'
console.log('end')

先打印start,创建img标签,onload是个callback函数,img的src被赋值。打印end。 当img的src被赋值后,就开始加载图片,图片加载完成时,打印loaded。

promise

// 获取第一份数据
$.get(url1, (data1) => {
    console.log(data1)
    // 获取第二份数据
    $.get(url2, (data2) => {
        console.log(data2)
        // 获取第三份数据
        $.get(url3, (data3) = >{
            console.log(data3)
            // 还可以继续获取更多的数据
        })
    })
})

callback hell 地狱般的存在,一层套一层。 因此产生了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 => cosnole.error(err))

问题解答

  • 同步和异步的区别是什么? JS是基于单线程语言;异步不会阻塞代码执行;同步会阻塞代码执行。
  • 手写用Promise加载一张图片
loadImg = (src) => {
	return 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
	})
}

const url = 'https://img4.sycdn.imooc.com/5b6a52020001c4bf03970457-140-140.jpg';

loadImg(url).then(img => {
	console.log(img.width)
	return img
}).then(img => {
	console.log(img.height)
}).catch(ex => console.error(ex))
  • 手写用Promise加载多张图片

loadImg = (src) => {
	return 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
	})
}

const url1 = 'https://img4.sycdn.imooc.com/5b6a52020001c4bf03970457-140-140.jpg';
const url2 = 'https://bkimg.cdn.bcebos.com/pic/a686c9177f3e67090ae08a613bc79f3df9dc5548?x-bce-process=image/resize,m_lfit,w_268,limit_1/format,f_jpg';


loadImg(url1).then(img1 => {
	console.log(img1.width)
	return img1
}).then(img1 => {
	console.log(img1.height)
	return loadImg(url2)
}).then(img2 => {
	console.log(img2.width)
	return img2
}).then(img2 => {
	console.log(img2.height)
}).catch(ex => console.error(ex))
  • setTimeout笔试题
// setTimeout 笔试题
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。

总结

  • 单线程和异步,异步和同步区别
  • 前端异步的应用场景:网络请求和定时任务
  • Promise 解决callback hell嵌套的问题

JS-Web-API-DOM

从JS基础到JS-Web-API

  • JS基础知识,规定语法(ECMA 262标准)
  • JS Web API,网页操作的API(W3C标准)
  • 前者是后者的基础,两着结合才能真正实际应用

JS Web API

  • DOM 操作
  • BOM 操作
  • 事件绑定
  • AJAX
  • 存储

DOM的本质

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

DOM操作(Document Object Model)

  • 题目
    DOM是哪种数据结构?
    DOM操作的常用API?
    attr和property的区别?
    一次性插入多个DOM节点,考虑性能,如何处理?
  • 知识点
    DOM本质:从html语言解析出来的一种树结构。
    DOM节点操作
    1)获取DOM节点
const div1 = document.getElementById('div1') // 元素
const divList = document.getElementsByTagName('div') // 集合
console.log(divList.length)
console.log(divList[0])
const containerList = document.getElementsByClassName('.container')
// 集合
const pList = document.querySelectorAll('p') // 集合  querySelectorAll 是个css选择器

2)DOM节点的attribute

const pList = document.querySelectorALL('p')
const p = pLsit[0]
p.setAttribute('data-name', 'imooc')
p.getAttribute('data-name')
p.setAttribute('style', 'font-size:30px;')
p.getAttribute('style')

3)DOM节点的property
获取DOM元素,然后通过js对象属性的形式来操作里面的style、className、nodeName这些。

const pList = document.querySelectorAll('p')
const p = pList[0]
console.log(p.style.width) // 获取样式
p.style.width = '100px'  // 修改样式
console.log(p.className) //获取class
p.className = 'p1' // 修改class

// 获取nodeName 和 nodeType
console.log(p.nodeName) // P
console.log(p.nodeType) // 1

property和attribute的区别参考

DOM结构操作
1)新增/插入节点

const div1 = document.getElementById('div1')
// 添加新节点
const p1 = document.createElement('p')
p1.innerHTML = 'this is p1'
div1.appendChild(p1) // 添加新创建的元素
// 移动已有节点,p2是已经存在的节点
const p2 = document.getElementById('p2')
div1.appendChid(p2)

2)获取子元素列表&获取父元素

// 获取父元素
const div1 = document.getElementById('div1')
const parent = div1.parentNode
// 获取子元素列表
const div1 = document.getElementById('div1')
console.log(div1.childNodes)
const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => child.nodeType === 1)

3)删除节点

const div1 = document.getElementById('div1')
const child = div1.childNodes;
div1.removeChild(child[0])

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 listNode = document.getElementById('list')
// 执行插入
for(let i=0; i<10; i++) {
    const li = document.createElement('li')
    li.innerHTML = `List item ${i}`
    // 每次都要操作DOM,频繁操作
    listNode.appendChild(li) 
}

// => 优化
const listNode = document.getElementById('list')
// 创建一个文档片段,此时还没插入到DOM树中
const frag = document.createDocumentFragment()
// 执行插入
for(let i=0; i<10; i++) {
    const li = document.createElement('li')
    li.innerHTML = `List item ${i}`
    frag.appendChild(li)
}
// 都完成后,再插入到DOM树中
listNode.aapendChild(frag)
  • 问题解答
    • DOM是哪种数据结构?
      树(DOM树)
    • DOM操作的常用API?
      DOM节点操作和DOM结构操作
    • attr和property的区别?
      property:修改对象属性,不会体现到html结构中
      attribute:修改html属性,会改变html结构
      俩者都有可能引起DOM重新渲染
  • 小结
    • DOM本质
    • DOM节点操作
    • DOM结构操作
    • DOM性能

JS-Web-API-BOM

BOM操作(Browser Object Model)

知识点

  • navigator 浏览器信息
const ua = navigator.userAgent // 看当前浏览器信息
const isChrome = ua.indexOf('Chrome')
console.log(isChrome)
  • screen 屏幕信息
console.log(screen.width)
console.log(screen.height)
  • location 地址信息,分析url信息
console.log(location.href)
console.log(location.protocol) // 'http:' 'https'
console.log(location.pathname)
console.log(location.search)
console.log(location.hash)
console.log(location.host)
  • history 浏览历史信息,前进后退
history.back()
history.forward()

题目

  • 如何识别浏览器的类型
const ua = navigator.userAgent // 看当前浏览器信息
const isChrome = ua.indexOf('Chrome')
console.log(isChrome)
  • 分析拆解url各个部分
console.log(location.href)
console.log(location.protocol) // 'http:' 'https'
console.log(location.pathname)
console.log(location.search)
console.log(location.hash)
console.log(location.host)

JS-Web-API-事件

事件绑定和事件冒泡

事件绑定

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

通用事件的绑定

function bindEvent(elem,type,fn) {
    elem.addEventListener(type, fn)
}
const a = document.getElementById('link1')
bindEvent(a, 'click', e => {
    console.log(event.target) // <a id='link1>一个连接</a>
    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>取消
    </div>
</body>
const p1 = document.getElementById('p1')
const body = document.body
bindEvent(p1, 'click')

事件代理

事件代理是基于事件冒泡来做的,有了事件冒泡机制才能实现事件代理。

<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.addEventLisener('click', e => {
    event.preventDefault() // 阻止默认跳转行为
    const target = e.target
    if(e.nodeName === 'A') {
        alert(target.innerHTML)
    }
})
  • 代码简洁
  • 减少浏览器内存占用
  • 不要滥用
  • 通用事件冒泡代理函数
// 普通绑定
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', function(event) {
    event.preventDefault() // 阻止默认行为
    alert(this.innerHTML)
})

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

// => 通用函数
function bindEvent(elem, type, selector, fn) {
    if(fn == null) {
    // fn如果为null,说明只传了3个参数
        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)
        }
    })
}

问题答疑和小结

  • 编写一个通用的事件监听函数
  • 描述事件冒泡的流程
    • 基于DOM树形结构
    • 事件会顺着触发元素往上冒泡
    • 引用场景:代理
  • 无限下拉的图片列表,如何监听每个图片的点击?
    • 事件代理
    • 用e.target获取触发元素
    • 用matches来判断是否是触发元素

JS-Web-API-Ajax

JS-Web-API-存储