导言
本篇文章会对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运算符
- 识别所有的值类型
- 识别函数
- 判断是否是引用类型(不可再细分)
// 判断所有的值类型
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来调试代码,可以按照下面的方法和步骤。
- 确保本地安装了node和npm
- 全局安装http-server模块
npm install http-server -g - terminal里启动server并且设置一个端口
http-server -p 8001 - 本地创建文件夹deepclone,并且创建index.html和deepclone.js文件。
- 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)
结果如下图所示。
- 变量计算-类型转换
- 字符串拼接
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){}
// 因为这里的a可能为null,可能为undefined,与null == 都能成立
- 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
- 问题整理
- typeof能判断哪些类型?
- 何时使用===何时使用==?
- 值类型和引用类型的区别
const obj1 = { x: 100, y: 200 }
const obj2 = obj1;
let x1 = obj1.x
obj2.x = 101
x1 = 102
console.log(obj1) // {x: 101}
- 手写深拷贝
注意判断值类型和引用类型;
注意判读是数组还是对象;
递归;
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
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结构操作
- 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来判断是否是触发元素