HTML
CSS
JavaScript
作用域
全局作用域
全局作用域中是否使用var声明变量区别:
使用var声明后无法使用delete删除
var a = 'hello'
b = 'world'
delete a
delete b
console.log(a) // hello
console.log(b) // b is not defined
注:全局作用域中生命的变量都会成为全局对象的属性
局部作用域(函数和块级)
DOM事件
事件模型
如上图所示,事件模型的顶端元素分别为window, document(即document.documentElement), body,其他Element
且分为三个阶段:Capture->Target->Bubble
事件模型的三个阶段可以通过
eventTarget事件对象的属性eventPhase查看
var phases = {
1: 'capture',
2: 'target',
3: 'bubble'
};
parent.addEventListener('click', function (e) { console.log(e.currentTarget.tagName, phases[e.eventPhase])}, true)
son.addEventListener('click', function (e) { console.log(e.currentTarget.tagName, phases[e.eventPhase])}, true)
son.addEventListener('click', function (e) { console.log(e.currentTarget.tagName, phases[e.eventPhase])}, false)
parent.addEventListener('click', function (e) { console.log(e.currentTarget.tagName, phases[e.eventPhase])}, false)
上述代码点击son时的输出为
// XXX capture
// YYY target
// YYY target
// XXX bubble
注:浏览器认为当前点击事件的对象为最深层元素,因此son元素捕捉和冒泡时都处于target阶段
事件绑定/注册
事件绑定的三种方法:
- HTML元素的on-属性(attribute)
<body onload="doSomething()">
</body>
注:值为执行函数,一定要带()
- DOM对象的on-方法(property)
window.onclick = function doSomething () {}
等价于window.setAtrribute("onclick",'doSomething()')
注:值为函数;只能绑定一个事件方法
⭐3. DOM对象的addEventListener方法
parent.addEventListener('click', function () {
console.log('parent clicked')
})
son.addEventListener('click', function () {
console.log('son clicked')
})
//下面方法等价于上方
son.addEventListener('click', function () {
console.log('son clicked')
}, false)
addEventListener有三个参数,第三个参数默认为false, 即表示冒泡模型。所以son元素点击事件输出为:
// son clicked
// parent clicked
将第三个参数改为true,则输出顺序相反。
事件委托(event delegation)
借用事件的冒泡特性和事件对象的target属性可以实现事件代理,即将一个元素组合的事件绑定在其父对象上。从而达到少些代码的目的(只写一个监听函数)
ul.addEventListener('click', function (e){
if (e.target.tagName === 'li') {
// do something...
}
}
注意区分属性target和currentTarget:
前者指向事件点击的对象,后者指向事件绑定的对象。
阻止事件传播的两个方法
- 仅阻止传播
stopPropagation
parent.addEventListener('click', function (e) {
e.stopPropagation() // 阻止向下传播
console.log('1')
}, true)
parent.addEventListener('click', function (e) {
console.log('2')
}, true)
输出结果
// 1
// 2
- 不仅组织传播还阻止向同元素的同类事件传播
stopstImmediatePropagation
parent.addEventListener('click', function (e) {
e.stopstImmediatePropagation() // 阻止向下传播
console.log('1')
}, true)
parent.addEventListener('click', function (e) {
console.log('2')
}, true)
输出结果
// 1
事件类型
鼠标事件
点击事件
mousedown->mouseup->click->mousedown->mouseup->dbclick
鼠标移动事件
mouseover->mouseenter->mouseout->mouseleave
资源和session历史事件
load(若从缓存中获取资源则不会触发)->pageshow->pagehide->beforeunload->unload(资源将不会保存在缓存中)
资源加载状态异常事件
error abort
其他事件:developer.mozilla.org/en-US/docs/…
想要直观了解这些概念,断点调试你值得拥有!
你想知道的事件、调用栈、作用域链、变量对象等等,都能在F12找到答案。
事件循环(Event Loop)
几个概念
单线程:JavaScript是单线程语言,一次只能做一件事,否则就要排队
异步:JS代码通常是顺序执行,如果不想阻塞后续代码执行,但又无法立即得到反馈,即选择使用异步代码
调用栈:函数调用、代码执行时的执行上下文(环境)栈
宏任务:需要排队的任务类型,包括且setTimeout/setInterval/setImmedode(Node独有)/requestAnimationFrame(浏览器独有)/IO
微任务:需要排队的任务类型,包括process.nextTick/Promise/MutationObserver
调用栈就是表演舞台,舞台有大咖有小咖。 表演顺序通常就是代码顺序,但是碰到异步类的演员,因为他们无法立即登台演出,则由导演负责舞台调度,将他们分类排序(宏任务/微任务)。 基本的规则就是:
- 演员依次上台演出,如果碰到异步演员,则分类排序,直到演出结束
- 演出结束后,导演cue微任务队列演员表演,直到演出结束
- 导演依次cue宏任务演员表演,知道表演结束 2-3循环执行,直到舞台为空、队列为空 注:若宏任务演员表演过程中产生新的微任务,则将他们加入到微任务队列队尾
在nodej中,宏任务和微任务队列还分了小组
小组优先级如下:
宏任务:setTimeout/setInterval>I/O(ajax)>setImmediate
微任务
nexttick>Promise
参考资料: segmentfault.com/a/119000001…
数据类型
数据类型判断
终极答案
function type(args){
return Object.prototype.toString.call(args)
.split(" ")[1].slice(0,-1).toLowerCase();
}
调用Object.prototype.toString.call(args)的输出结果为[object ${ObjectType}]
特别提醒:toString方法在Function对象中被重写
Object.toString =/= Object.prototype.toString
Object.toString === Function.toString
其他方法
| 方法 | 判定类型 | 返回值 | 备注 |
|---|---|---|---|
| 关键字typeof | 值类型 | 字符串 | 1.null的返回值为object; 2.函数类型的发返回值为function; 3.其他对象类型的返回值都为object |
| 关键字instanceof | 对象类型 | 布尔值 | 父子类型都为true |
| 对象属性constructor | 对象类型 | 函数 |
隐式类型转换
见我的github和上一篇文章
深浅拷贝
对象类型中最常使用到的是数组和对象类型。
数组类型的简单拷贝
//1. var newArr = oddArr.concat()
//2. var newArr = oddArr.slice()
对象类型的简单拷贝
// 1.
var newObj = Object.assign({}, oldObj)
// 2.
function shallowCopy (obj) {
var newObj = {}
for(var prop in obj) {
if(obj.hasOwnProperty(attr)){
newObj[prop] = obj[prop]
}
}
return newObj
}
// 3. JSON对象中的stringify和parse(不能解决无法复制方法的问题)
// 4. 深拷贝,递归严谨版
function cloneDeep(obj, hash = new WeakMap()) {
// 1
if (obj == null) return obj;
// 2
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 3 原始类型和函数类型的处理
// 由于操作函数的行为一般只是执行,不涉及到修改,故返回即可
if (typeof obj !== "object") return obj;
// 4 循环引用
if (hash.has(obj)) return hash.get(obj);
// 5 处理对象和数组
// 克隆后,使用弱引用WeakMap以对象作为key,将值存起来
const result = new obj.constructor();
hash.set(obj, result);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = cloneDeep(obj[key], hash);
}
}
return result;
}
// 5. 直接调用库函数,例如lodash的cloneDeep函数
常用的javascript库包括underscore、lodash
原型和原型链
所谓原型链即访问某属性和方法时,从对象本身查找,如没找到则沿着__proto__逐层查找,直到找到终点Object.prototype(Object.prototype.proto=null)。
原型链是怎么构成的?
- 对象是可扩展的,即可自由添加属性和方法
- 所有对象都有__proto__属性,属性值为一个普通对象,即其'原型'对象
- 所有函数都有prototype属性,属性值仍然是一个普通对象
- 函数也是对象
- 所有函数(包括构造函数)的__proto__指向对象Function.prototype
- 对象的__proto__对象指向其构造函数的prototype对象
执行上下文和执行栈
执行上下文类型
全局执行上下文(以浏览器环境为例)
- 创建全局对象window
- 确定this指向为window
全局上下文只有唯一的一个,在浏览器关闭时出
函数执行上下文
创建->执行->回收
创建阶段:
- 创建变量对象:初始化变量arguments,函数提升和变量提升
- 创建作用域链(Scope Chain):作用域链包含变量对象
- 确定this指向
执行阶段: 变量赋值、代码执行
回收: 执行上下文出栈等待回收
eval函数上下文
不常用,略
变量/函数提升
函数提升是整体提升,变量提升是声明提升。 当函数和变量同名时,函数优先级高。注意下面一道题
console.log(foo) // function foo
function foo(){
console.log("function foo")
}
var foo = "var foo"
console.log(foo) // var foo
this指向
"this的值是在执行的时候才能确认,定义的时候不能确认! 为什么呢 —— 因为this是 执行上下文环境的一部分(环境对象),而执行上下文需要在代码执行之前确定,而不是定义的时 候。"
详情及来源见 www.yuque.com/snb/efe/dco…
注意区分apply、call和bind的区别
前二者是直接调用函数,后者仍然返回函数
作用域链和闭包
作用域链
JavaScript中的每一个执行环境都有一个与之相关的变量对象(也叫做活动对象,见下图),环境中定义的所有变量和函数都保存在这个对象中。
作用域链:如果访问一个变量或函数,在当前作用域没有找到相应的标识符,则回到其父作用域中找,直到找到或到作用域链的尽头。如果最终没找到,则会抛出ReferenceError。
延长作用域
- with
- catch
- apply/call/bind
闭包
闭包是指有权访问另一个函数作用域变量的函数。
一般用在返回函数的函数中,例如下方:
function foo() {
var msg = 'i'm foo'
function bar() {
console.log(msg)
}
return bar
}
var bab = foo()
bab() // i'm foo
在foo生命周期结束后,bab仍能访问foo中的变量
闭包的用处:实现私有变量
function Person(){
var name = 'moya';
this.getName = function(){
return name;
}
this.setName = function(value){
name = value;
}
}
var p = new Person()
console.log(p.getName())
p.setName("momoda")
console.log(p.getName())
闭包存在的问题:内存泄漏
更多参考链接:www.yuque.com/fundebug/rd…
ES6新特性
Array增强
- Array.of(): 将一组参数转换为数组,弥补构造函数Array的行为不统一的问题
// ES5实现
function toArray() {
return [].slice.call(arguments)
}
- Array.from():将类数组或可遍历对象转换为数组
// ES5实现
var arr=[].slice.call(arrayLike)
参考文档:es6.ruanyifeng.com/#docs/array
异步解决方案
await
Promise
手写代码
apply
call
bind
new
instanceof
节流
防抖
promise
ajax
apply
发布订阅
Vue
吐槽: 为什么编辑的时候没有折叠功能?文章长了要拉好长时间。