莫莫哒的一轮复习笔记

220 阅读8分钟

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事件

事件模型

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阶段

事件绑定/注册

事件绑定的三种方法:

  1. HTML元素的on-属性(attribute)
<body onload="doSomething()">
</body>

注:值为执行函数,一定要带()

  1. 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...
    }
}

注意区分属性targetcurrentTarget: 前者指向事件点击的对象,后者指向事件绑定的对象。

阻止事件传播的两个方法

  1. 仅阻止传播stopPropagation
parent.addEventListener('click', function (e) {
	e.stopPropagation() // 阻止向下传播
	console.log('1')
}, true)
parent.addEventListener('click', function (e) {
	console.log('2')
}, true)

输出结果

// 1
// 2
  1. 不仅组织传播还阻止向同元素的同类事件传播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

调用栈就是表演舞台,舞台有大咖有小咖。 表演顺序通常就是代码顺序,但是碰到异步类的演员,因为他们无法立即登台演出,则由导演负责舞台调度,将他们分类排序(宏任务/微任务)。 基本的规则就是:

  1. 演员依次上台演出,如果碰到异步演员,则分类排序,直到演出结束
  2. 演出结束后,导演cue微任务队列演员表演,直到演出结束
  3. 导演依次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)。

原型链是怎么构成的?

  1. 对象是可扩展的,即可自由添加属性和方法
  2. 所有对象都有__proto__属性,属性值为一个普通对象,即其'原型'对象
  3. 所有函数都有prototype属性,属性值仍然是一个普通对象
  4. 函数也是对象
  5. 所有函数(包括构造函数)的__proto__指向对象Function.prototype
  6. 对象的__proto__对象指向其构造函数的prototype对象

执行上下文和执行栈

执行上下文类型

全局执行上下文(以浏览器环境为例)

  1. 创建全局对象window
  2. 确定this指向为window

全局上下文只有唯一的一个,在浏览器关闭时出

函数执行上下文

创建->执行->回收

创建阶段:

  1. 创建变量对象:初始化变量arguments,函数提升和变量提升
  2. 创建作用域链(Scope Chain):作用域链包含变量对象
  3. 确定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

吐槽: 为什么编辑的时候没有折叠功能?文章长了要拉好长时间。