JavaScript-big


01、JavaScript数据类型及存储方式

数据类型

JavaScript共有8种数据类型。
7种基本数据类型:NullUndefinedBooleanNumberStringSymbol(ES6新增,表示独一无二的值)和BigInt(ES10新增)。
1种引用数据类型:Object。Object里面包含ObjectArrayFunctionDateRegExp等。
总结:JavaScript不支持任何创建自定义类型的机制,而所有值最终都将是上述8种数据类型之一。


存储方式

1、原始数据类型直接存储在栈(stack)中,占据空间小且大小固定,属于被频繁使用的数据,所以放入栈中存储。
2、引用数据类型同时存储在栈(stack)和堆(heap)中,占据空间大,且大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。


原文链接
1、原文


02、var、let和const的区别

区别

1、var声明的变量会挂载在window上,而let和const声明的变量不会。
2、var声明变量存在变量提升,let和const不存在变量提升。
3、let和const声明形成块作用域。
4、同一作用域下var可以声明同名变量,而let和const不可以。


相关链接
1、原文
2、掘金-原文


03、null和undefined的区别

区别

unll:用来初始化一个变量,这个变量可能赋值为一个基本类型值或者引用类型值。undefined:声明一个变量,但是没有赋值。
1、null表示一个变量被人为的设置为空对象, 而不是原始状态。
2、 undefined表示一个变量最原始的自然状态值。
3、在实际使用过程中,为了保证变量所代表的语义,不要对一个变量赋值为undefined,当需要释放一个对象时,直接赋值为null即可。


原文链接
1、原文


04、防抖debounce

多种不同的解释或定义

1、防抖是指短时间内多次触发,最终在停止触发后的某个指定时间执行一次函数,只执行一次。
2、防抖是指触发事件n秒后才执行函数,如果在n秒内又触发事件,则会重新计算函数执行时间。
3、防抖是n秒内重复的触发会导致重新计时,直到n秒内没有重复触发函数才会执行。


原文链接
1、原文


05、节流throttle

多种不同的解释或定义

1、短时间内多次触发,即使触发仍在继续也可以根据指定。时间触发一次函数,至少执行一次。
2、节流是指连续触发事件,但是在n秒中只执行一次函数。节流会稀释函数的执行频率。
3、节流是n秒内只会执行第一次触发的函数,重复的触发无效。


原理

主要的原理就是在闭包内设置一个标记,在限定的时间内这个flag设置为true,函数再次点击则让执行,setTimeout函数执行以后将flag设置为flase,就可以继续执行 。


相关原文链接
1、原文
2、原文


06、克隆、拷贝

定义

1、浅拷贝只是增加了一个指针指向已存在的内存地址。
2、深拷贝是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存。


原文链接
1、浅浅克隆和浅克隆
2、深克隆
3、深克隆


07、作用域和作用域链

作用域定义

作用域是指程序中定义变量的区域,它决定了当前执行代码对变量的访问权限。


作用域链的定义

查找变量的时候,先从当前上下文的变量对象中查找,如果没有找到,就向父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象,如果全局上下文对象中也没有找到变量,则返回undefined。这样由多个执行上下文的变量对象构成的链表就是作用域链。


相关文章及原文
1、作用域/作用域链
2、作用域-笔试题
3、参考文章


08、闭包

多种不同的定义

1、闭包是指有权访问另一个函数作用域中的变量的函数。
2、闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
3、闭包可以让一个函数访问并操作其声明时的作用域中的变量和函数,即使声明时的作用域消失了,也可以调用。
4、闭包是一个定义在其它函数(父函数)里面的函数,它拥有对父函数里面变量的访问权。闭包有三个作用域的访问权。自身的作用域、父作用域和全局作用域。
5、闭包就是能够读取其他函数内部变量的函数。


原文链接
1、闭包的定义


09、原型和原型链

概念

在JavaScript中,对象都有__proto__属性(隐式原型),指向构造该对象的构造函数的原型,而函数Function比较特殊,它除了和其他对象一样有__proto__属性外,还有自己特有的属性prototype称之为原型对象,原型对象有一个constructor属性,该属性指回该函数本身。


关键字介绍

1、每个函数都会有prototype属性,普通对象没有prototype属性。prototype是构造函数的原型对象。
2、每个对象都有双下划线__proto__属性,因为函数也是对象,所以函数也有双下划线__proto__属性。它指向构造函数的原型对象。
3、constructor是原型对象上的一个指向构造函数的属性。


原文链接
1、原型与原型链-总结


10、new操作符

new操作符做的事情

1、创建一个全新的对象,也就是使用Object.create(null)创建了无原型的对象。目的是保存new出来的实例的所有属性。
2、将构造函数的原型赋值给新创建的对象的原型。目的是将构造函数原型上的属性继承下来。
3、调用构造函数,并将this指向新建的对象。目的是让构造函数内的属性全部转交到该对象上,使得this指向改变,方法有三:applycallbind
4、判断构造函数调用的方式,如果是new的调用方式,则返回经过加工后的新对象,如果是普通调用方式,则直接返回构造函数调用时的返回值。


通过new的方式创建对象和通过字面量创建对象有什么区别

对象都是通过new产生。function Foo() {},function是语法糖,内部等同于new Function()。
let object = { number: 1 } ,使用字面量创建对象,内部也是使用了new Object()。
对于创建一个对象来说,更推荐使用字面量的方式创建对象。因为使用new Object()的方式创建对象,需要通过作用域链一层层找到Object,如果使用字面量的方式就没有这个问题。


原文链接
1、原文


11、两等(==)和非(!)的优先级比较

代码示例

console.log([] == ![]); 
// true

解析

1、!(非) 的优先级高于两等(==),右边[ ]是true,取返等于false 。
2、一个引用类型值和一个基本类型值作比较,会把引用类型转化成基本类型值。所以,[ ] => 0。
3、最后0 == false的结果是true。


原文链接
1、原文


12、数据类型转换

三种类型转换

1、转换为布尔值,调用Boolean()方法。
2、转换为数字,调用Number()、parseInt()和parseFloat()方法。
3、转换为字符串,调用.toString()或者String()方法。


原文链接
1、原文


13、检测数据类型的方法

最常见的判断方法:typeof

注意:其中typeof返回的类型都是字符串形式。


console.log(typeof "hello world"); 
// => "string"     
console.log(typeof 'undefined'); 
// => "string"
console.log(typeof 123); 
// => "number"
console.log(typeof true); 
// => "boolean"
console.log(typeof undefined); 
// => "undefined"
console.log(typeof Symbol()); 
// => "symbol"
console.log(typeof null); 
// => "object"
console.log(typeof [1,2,3]); 
// => "object"
console.log(typeof new Date()); 
// => "object"
console.log(typeof new RegExp()); 
// => "object"
console.log(typeof new Function()); 
// => "function"

已知是对象类型:instanceof

注意instanceof后面一定要是对象类型,并且大小写不能错,该方法适合一些条件选择或分支。


console.log([1,2,3] instanceof Array); 
// => true
console.log(new Date() instanceof Date); 
// => true
console.log(new Function() instanceof Function); 
// => true
console.log(null instanceof Object); 
// => false

根据对象原型链检测:Object.prototype.toString.call()

1、适用于所有类型的判断检测,注意区分大小写.toString()方法,在Object原型上返回数据格式。
2、原生检测方法,不存在兼容性问题。
3、call()调用且改变this指向,因为大部分数据类型都有自己的toString()方法。
4、toString()可以输出一个对象的内部属性class,查看对象的类型名。


console.log(Object.prototype.toString.call("123")); 
// => [object String]
console.log(Object.prototype.toString.call(123)); 
// => [object Number]
console.log(Object.prototype.toString.call(true)); 
// => [object Boolean]
console.log(Object.prototype.toString.call(null)); 
// => [object Null]
console.log(Object.prototype.toString.call(undefined)); 
// => [object Undefined]
console.log(Object.prototype.toString.call(Symbol())); 
// => [object Symbol]
console.log(Object.prototype.toString.call([1, 2, 3])); 
// => [object Array]
console.log(Object.prototype.toString.call({name: 'Hello'})); 
// => [object Object]
console.log(Object.prototype.toString.call(function () {})); 
// => [object Function]
console.log(Object.prototype.toString.call(new Date())); 
// => [object Date]
console.log(Object.prototype.toString.call(/\d/)); 
// => [object RegExp]

根据对象的constructor进行检测

constructor判断方法跟instanceof相似,只是检测Object与instanceof不一样,constructor还可以处理基本数据类型的检测,不仅仅是对象类型。

注意

1、nullundefined没有constructor
2、判断数字时使用圆括号,比如(123).constructor,如果写成123.constructor会报错;
3、constructor在类继承时会出错,因为Object会被覆盖掉,检测结果就不对。


// 注意当出现继承的时候,使用constructor会出现问题
function A() {};
function B() {};
A.prototype = new B(); // A继承自B
console.log(A.constructor === B); 
// => false
var C = new A();
// 现在开始判断C是否跟A的构造器一样
console.log(C.constructor === B); 
// => true
console.log(C.constructor === A); 
// => false 
// 解决这种情况,通常是手动调整对象的constructor指向
// 将自己的类赋值给对象的constructor属性
C.constructor = A;
console.log(C.constructor === A); 
// => true
console.log(C.constructor === B); 
// => false

jQuery方法:jquery.type()/$.type()

据说是无敌万能的方法,如果对象是nullundefined,直接返回'null''undefined'
注意:在使用时,一定要引入jQuery文件,不然会报错,jQuery is not defined**


console.log(jQuery.type(undefined) === "undefined"); 
// => true
console.log(jQuery.type() === "undefined"); 
// => true
console.log(jQuery.type(window.notDefined) === "undefined"); 
// => true
console.log(jQuery.type(123) === "number"); 
// => true
console.log(jQuery.type('123') === "string"); 
// => true
console.log(jQuery.type([]) === "array"); 
// => true
console.log(jQuery.type(true) === "boolean"); 
// => true
console.log(jQuery.type(function(){}) === "function"); 
// => rue
console.log(jQuery.type(new Date()) === "date"); 
// => true
console.log(jQuery.type(/\d/) === "regexp"); 
// => true
console.log(jQuery.type(new Error()) === "error"); 
// => true jQuery 版本高于 1.9.3
console.log(jQuery.type({name:'Hello'}) === "object"); 
// => true
console.log(jQuery.type(Symbol()) === "symbol"); 
// => true

原文链接
1、原文


14、检测一个对象是不是数组

源数据

// 基本数据类型
let number = 100;
let string = 'asdfghjkl';
let boolean = true;
let nu = null;
let un = undefined;

// 引用数据类型
let fun = function() {};
let object = {};
let array = [3, 6, 9];
let date = new Date();

proto

console.log(object.__proto__ == Array.prototype); 
// false
console.log(array.__proto__ == Array.prototype); 
// true
console.log(date.__proto__ == Array.prototype); 
// false

Object.getPrototypeOf([value])

// __proto__可能被浏览器禁用,所以有等效的函数检测。
console.log(Object.getPrototypeOf(object) == Array.prototype); 
// false
console.log(Object.getPrototypeOf(array) == Array.prototype); 
// true
console.log(Object.getPrototypeOf(date) == Array.prototype); 
// false

Array.prototype.isPrototypeOf([value])

console.log(Array.prototype.isPrototypeOf(object)); 
// false
console.log(Array.prototype.isPrototypeOf(array)); 
// true
console.log(Array.prototype.isPrototypeOf(date)); 
// false

使用构造函数constructor进行检测,即使用父级原型对象中的constructor属性

console.log(object.constructor == Array); 
// false
console.log(array.constructor == Array); 
// true
console.log(date.constructor == Array); 
// false

[value] instanceof Array

console.log(object instanceof Array); 
// false
console.log(array instanceof Array); 
// true
console.log(date instanceof Array); 
// false

万能方法,不存在兼容性:[value].prototype.toString().call()

console.log(Object.prototype.toString.call(object)); 
// [object Object]
console.log(Object.prototype.toString.call(array)); 
// [object Array]
console.log(Object.prototype.toString.call(date)); 
// [object Date]

isArray():ES5新增,存在兼容性问题。

console.log(Array.isArray(object)); 
// => false
console.log(Array.isArray(array)); 
// => true
console.log(Array.isArray(date)); 
// => false

原文链接
1、原文


15、this

改变this指向之call/apply/bind

相同点
都可以改变this指向。
不同点
1、callapply会调用函数,并且改变函数内部this指向。
2、callapply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递参数。
3、bind不会调用函数,可以改变函数内部this指向。
应用场景
1、call经常做继承。
2、apply经常跟数组有关系。比如,借助于数学对象实现数组最大值最小值。
3、bind不调用函数。但是可以改变this指向。比如改变定时器内部的this指向。


this总结

1、函数是否是用new来调用?如果是则this绑定新创建的对。
2、函数是否通过call或者apply来硬绑定调用?如果是则this绑定的是指定的对象。
3、函数是否是被某个上下文对象调用?如果是则this绑定这个上下文对象。
4、如果以上都不是,则使用默认绑定,点前是谁就是谁,函数调用一般指向window比较多。


this带图

1. 在浏览器里,在全局范围内this指向window对象。
2. 在函数中,this永远指向最后调用他的那个对象。
3. 构造函数中,this指向new出来的那个新的对象(实例)。
4. callapplybind中的this被强行绑定在指定的那个对象上。
5. 箭头函数中的this比较特殊,箭头函数的this指向父作用域中的this,不是调用时的this。要知道前四种方式,都是调用时确定,也就是动态的this。而箭头函数是静态的this,声明的时候就确定了。
6. callapplybind都是函数内置的一些API,调用时可以为函数指定this的执行,同时也可以传参。


原文链接
1、6种函数调用方式的this指向
2、改变this指向之call/apply/bind
3、this总结-06
4、this带图


16、ES6及往后版本的新特性

序号关键字功能
1序号let
2const定义常量
3箭头函数() => a + b;
4解构赋值let { a, b, c} = obj;
5模板字符串``
6扩展运算符
7函数参数默认值function a(a = 0, b = 0) { return a + b; }
8函数参数不定参function add(...num){ return num.reduce((result, value) => result + value) } add(1, 2, 3); // 6
9class类的定义class Point { constructor(x, y) { this.x = x; this.y = y; } }

17、箭头函数与普通函数有什么区别


18、字符串常用方法

序号方法说明result
1slice下标截取字符串此方法传入的参数是字符串下标,第一个参数要比第二个参数小,否则会返回不确定的值,截取的值包含第一个参数,不包含第二个参数。
2substr下标和长度截取字符串此方法传入下标和长度,第一个参数传入下标,截取的值包含下包,第二个参数是截取长度
3substring下标截取字符串此方法传入的参数是字符串下标,两个参数不区分大小,因为此方法会自动找到最小下标开始截取到结束下标,截取的值不包含结束下标
4split字符截取字符串根据传入的参数对字符串进行分割,并且返回数组
5replace替换字符串内容用另一个值替换在字符串中指定的值

19、数组常用方法

序号API功能描述是否改变原数组
01unshift此方法是将一个或多个元素添加到数组的开头,并返回新数组的长度Y
02push此方法是在数组的后面添加新加元素,此方法改变了数组的长度Y
03shift此方法删除数组第一个元素,并返回数组,此方法改变了数组的长度Y
04pop此方法删除数组最后一个元素,并返回数组,此方法改变了数组的长度Y
05join此方法将数组转为字符串并返回转化的字符串数据,不会改变原来的数组N
06spliceArray.splice(开始位置, 删除的个数,元素) ,万能方法,可以实现增删改Y
07slice此方法截取指定位置的数组,并且返回截取的数组,不会改变原数组N
08map此方法是将数组中的每个元素调用一个提供的函数,结果作为一个新的数组返回,并没有改变原来的数组N
09forEach此方法是将数组中的每个元素执行传进提供的函数,没有返回值,注意和map方法区分N
10filter此方法是将所有元素进行判断,将满足条件的元素作为一个新的数组返回N

20、indexOf和findIndex的区别

概述

indexOf:方法返回在数组中可以找到一个给定元素的第一个索引(下标),如果不存在,返回-1
findIndex:方法返回数组中满足提供的测试函数的第一个元素的索引(下标),如果不存在,返回-1


indexOf代码示例

let dataA1 = ['王维', '范仲淹', '欧阳修', '李商隐', '孟浩然'],
	dataA2 = [{ sname: '陶渊明' }, { sname: '贺知章' }],
	variate = { sname: '岑参' };

console.log(dataA1.indexOf('欧阳修')); // 2
console.log(dataA2.indexOf({ sname: '陶渊明' })); // -1
console.log([{ sname: '龚自珍' }, variate].indexOf(variate)); // 1

findIndex代码示例

let dataArray = [
	{ id: 1, sname: '陆游', alias: '务观', nickname: '放翁' },
	{ id: 2, sname: '屈原', alias: '原', nickname: null },
	{ id: 3, sname: '王安石', alias: '介甫', nickname: '半山' }
];

console.log(dataArray[dataArray.findIndex((item) => item.id == 3)]);
// {id: 3, sname: "王安石", alias: "介甫", nickname: "半山"}
// 注意:普通函数写法记得加 return
console.log(dataArray[dataArray.findIndex(function(item) { return item.id == 2; })]);
// {id: 2, sname: "屈原", alias: "原", nickname: null}

indexOf与findIndex的区别

indexOf:查找值作为第一个参数,采用绝对相等进行比较,更多的是用于查找基本类型的数据,如果是引用类型,则是判断是否是同一个对象的引用。
findIndex:比较函数作为第一个参数,多用于非基本类型的数组索引查找,或查找条件很复杂。


indexOf与findIndex的相同点

indexOffindIndex都是查找数组中满足条件的第一个元素的索引(下标)。


相关链接
1、源码实现
2、原文-1


21、双等与三等有什么区别


22、事件委托、代理

1、多种不同的定义

1、通俗的讲,事件委托就是把一个元素响应事件(click、keydown等)的函数委托到外层元素上。
2、简单的讲,事件委托就是利用JavaScript事件冒泡的特性,将内层元素的事件委托(绑定)给外层元素处理。
3、一般来讲,事件委托会把一个或者一组元素的事件委托(绑定)到它的父层元素或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。


2、原文链接
1、普通事件和事件委托


23、JavaScript之继承

1、原型直接继承
1、原型直接继承原型直接继承
2、知乎-原文


2、原型实例继承
1、原型实例继承
2、知乎-原文


3、构造函数继承
1、构造函数继承
2、知乎-原文


4、ES6的class继承
1、ES6的class继承
2、知乎-原文


5、原型实例继承和原型直接继承的区别
1、原型直接继承

student.prototype = parentClass.prototype;
student.prototype.constructor = student;

2、原型实例继承

student.prototype = new parentClass();
student.prototype.construct = student;

3、相关链接
1、原型实例继承和原型直接继承的区别


18、图片懒加载

css部分

.img_box {
	margin-top: 30px;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
	border: 1px solid red;
}
        
.img_box>img {
	width: 450px;
	height: 500px;
	margin: 5px 0;
}

html部分

<div class="img_box">
	<!-- 加载loading图片是在html部分实现 -->
	<!-- src存放的是伪图片,等待图片 -->
	<!-- data-src是自定属性,存放真是的图片地址 -->
	<img src="/img/loading02.gif" data-src="/img/01.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/02.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/03.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/04.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/05.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/06.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/07.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/08.jpg" alt="">
	<img src="/img/loading02.gif" data-src="/img/09.jpg" alt="">
</div>

JavaScript部分

// onload是等所有的资源文件加载
// 完毕以后再绑定事件
window.onload = function() {
	// 1、获取一面图片标签元素
	// 获取图片列表,
	// 即img标签列表
	let imgs = document.querySelectorAll('img');

	// 2、获取到浏览器顶部的距离
	function getTop(e) {
		return e.offsetTop;
	};

	// 3、懒加载实现
	function lazyload(dataImg) {
		// 3.1、获取可视区域高度
		let innerHeight = window.innerHeight;
		// 3.2、获取滚动区域高度
		let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;

		for (let i = 0; i < dataImg.length; i++) {
			// 3.3、如果图片距离顶部的距离大于
			// 可视区域和滚动区域之和时触发懒加载
			if ((innerHeight + scrollTop) > getTop(dataImg[i])) {
				// 3.4、真实情况是页面开始有1秒空白,
				// 所以使用setTimeout定时1s
				(function(i) {
					setTimeout(function() {
						// 3.5、不加立即执行函数i会等于9
						// 隐形加载图片或其他资源,
						// 创建一个临时图片,
						// 这个图片在内存中不会到页面上去。
						// 实现隐形加载
						let temp = new Image();
						// console.log('new:', temp);

						// 3.6、只会请求一次
						temp.src = dataImg[i].getAttribute('data-src');
						// console.log('src:', temp);

						// 3.7、onload判断图片加载完毕,
						// 真实图片加载完毕,
						// 再赋值给DOM节点
						temp.onload = function() {
							// 3.8、获取自定义属性data-src,
							// 用真图片替换假图片
							dataImg[i].src = dataImg[i].getAttribute('data-src');
							// console.log('dataImg:', dataImg[i].src);
						};
					}, 1000);
				})(i);
			}
		}
	};

	// 4、调用懒加载函数,
	// 传递图片标签元素
	lazyload(imgs);

	// 5、滚动监听
	window.onscroll = function() {
		// 调用懒加载函数
		lazyload(imgs);
	};
};

相关链接
1、原文


20、JavaScript实现内置方法

原文链接
01、原文-mypush
02、原文-mymap
03、原文-myforEach
04、原文-myjoin
05、原文-myfindIndex
06、原文-myevery
07、原文-mysort
08、原文-myreduce
09、原文-myapply
10、原文-mybind
11、原文-mycall


JavaScript内置方法总结
1、掘金
2、掘金


21、函数柯理化

21.1、参数复用

// 普通函数验证
function check(regExp, text) {
    return regExp.test(text);
};

console.log(check(/^\d+$/g, '123')); 
// true
console.log(check(/^\d+$/g, '2d')); 
// false

console.log(check(/^[a-z]+$/g, 'text')); 
// true
console.log(check(/^[a-z]+$/g, '3d')); 
// false

// --------------------------------------------------------------------------

// Currying后
function curryingCheck(regExp) {
    return function(regExp) {
        return reg.test(regExp);
    };
};

let hasNumber = curryingCheck(/^\d+$/g),
	hasLetter = curryingCheck(/^[a-z]+$/g);

console.log(hasNumber('159')); 
// true
console.log(hasNumber('2d')); 
// false

console.log(hasLetter('3d')); 
// false
console.log(hasLetter('text')); 
// true

示例是一个正则的校验,正常来说直接调用check函数就可以,但是如果有很多地方都要校验是否有数字,其实就是需要将第一个参数reg进行复用,这样别的地方就能够直接调用hasNumber,hasLetter等函数,让参数能够复用,调用起来也更方便。


21.2、提前确认(惰性函数)

// 方案一
let on = function(element, event, handler) {
    if (document.addEventListener) {
        if (element && event && handler) {
            element.addEventListener(event, handler, false);
        };
    } else {
        if (element && event && handler) {
            element.attachEvent('on' + event, handler);
        };
    };
};

// 方案二
// ()(); => ~function() {.. ..}();
let on = (function() {
    if (document.addEventListener) {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.addEventListener(event, handler, false);
            };
        };
    } else {
        return function(element, event, handler) {
            if (element && event && handler) {
                element.attachEvent('on' + event, handler);
            };
        };
    };
})();

// 方案三
// 换一种写法可能比较好理解一点,
// 上面就是把isSupport这个参数给先确定下来了
let on = function(isSupport, element, event, handler) {
    isSupport = isSupport || document.addEventListener;
    if (isSupport) {
        return element.addEventListener(event, handler, false);
    } else {
        return element.attachEvent('on' + event, handler);
    };
};

在做项目的过程中,封装一些dom操作可以说再常见不过,上面第一种写法也是比较常见,但是看看第二种写法,它相对于第一种写法就是自执行然后返回一个新的函数,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断。


21.3、延迟运行

Function.prototype.bind = function (context) {
    let that = this,
    	args = Array.prototype.slice.call(arguments, 1);
 
    return function() {
        return that.apply(context, args);
    };
};

js中经常使用的bind,实现的机制就是Currying。


21.4、经典面试题

实现一个add()方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;


function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    let _args = Array.prototype.slice.call(arguments);
    
    // 在内部声明一个函数,
    // 利用闭包的特性保存_args并收集所有的参数值
    let _adder = function() {
        _args.push(...arguments);
        return _adder;
    };
    
    // 利用toString隐式转换的特性,当最后执行时隐式转换,
    // 并计算最终的值返回
    // toString是toScript()方法中的一个属性
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    };
    
    return _adder;
};

console.log(add(1)(2)(3)); 
// 6
console.log(add(1, 2, 3)(4)); 
// 10
console.log(add(1)(2)(3)(4)(5)); 
// 15
console.log(add(2, 6)(1)); 
// 9

21.5、原文链接
1、函数柯理化


22、任务(事件)队列和事件循环

22.1、定义
22.1.1、任务(事件)队列

JavaScript中有两类任务(事件)队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列可以有多个,微任务队列只有一个。


宏仁务(macro tasks

1. setTimeout
2. setInterval
3. script
4. I/O
5. UI 交互事件
6. postMessage
7. MessageChannel
8. setImmediate(Nodejs环境)


微任务(micro tasks)

1. Promise.then
2. Object.observe
3. MutaionObserve
4. process.nextTick(Node.js环境)


22.1.2、事件循环

事件循环(Event Loop)遵循的是HTML5的标准。当执行栈(stack)为空的时候,就会从任务队列中,取任务来执行。共3步:
1、取一个宏任务来执行。执行完毕后,下一步。
2、取一个微任务来执行,执行完毕后,再取一个微任务来执行。直到微任务队列为空,执行下一步。
3、更新UI渲染。
事件循环(Event Loop)会无限循环执行上面的3步,这就是事件循环(Event Loop)的主要控制逻辑。其中,第3步(更新UI渲染)会根据浏览器的逻辑,决定要不要马上执行更新。毕竟更新UI成本大,所以,一般都会比较长的时间间隔,执行一次更新。


22.2、示例
22.2.1、示例-1

setTimeout(() => {
	console.log(1);
}, 20);

console.log(2);

setTimeout(() => {
	console.log(3);
}, 10);

console.log(4);

// console.time('time');
for (let i = 0; i < 90000000; i++) {
	// 900000 5 个 0:2.57421875 ms 左右
	// 9000000 6 个 0:12.625 ms 左右
	// 90000000 7 个 0:116.13525390625 ms 左右
	// 900000000 8 个 0:525.622314453125 ms 左右
	// 9000000000 9 个 0:9807.490966796875 ms 左右
	// 90000000000 10 个 0:循环不出来了
}

// console.timeEnd('time');
console.log(5);

setTimeout(() => {
	console.log(6);
}, 8);

console.log(7);

setTimeout(() => {
	console.log(8);
}, 15);

console.log(9);
// 2 4 5 7 9   3 1 6 8

循环的时间都超过了所有定时器的时间,在宏仁务中,定时器会按照从上到下的顺序执行,不再按照设置的时间长短来执行。


22.2.2、示例-2

console.log(1);

setTimeout(()=>{
	console.log(2);
}, 50);

console.log(3);

setTimeout(()=>{
	console.log(4);
	while(1 === 1) {};
	// 遇到死循环,
	// 所有代码执行都是在主栈中执行,
	// 主栈永远结束不了,
	// 后面啥都不干
}, 0);

console.log(5);
// 1 3 5   4

22.2.3、示例-3

console.log(1);

// 宏仁务
setTimeout(function () {
	console.log(2);
}, 0);

// 微任务
Promise.resolve().then(function () {
		console.log(3);
	}).then(function () {
		console.log(4);
});

console.log(5);
// 1 5   3 4 2

22.3、原文链接
1、定义
2、示例


23、正则表达式

1. 正则(RegExp)


24、实现浏览器内多个标签页(.html 页面)之间的通信

1、cookies
2、localStorge
3、webSocket
4、SharedWorker


25、面向对象(OOP)

封装

// 使用字面量定义不需要反复创建的对象
var object = { Uname: '张三', Uage: 56 };

// 如果一个对象需要反复创建,使用构造函数即可
// 第一步:定义构造函数
function Student(name, age) { 
	this.name = name; 
	this.age = age; 
	// 不能将方法(函数)定义在构造函数中,
	// 因为会被多次创建,每创建一个实例就会创建一次方法。
};

// 第二步:用new调用构造函数
let newFun = new Student('半晨', '28');
console.log(newFun);
// Student { name: '半晨', age: '28' }
console.log(newFun.name);
// 半晨
console.log(newFun.age);
// 28

继承

所有子对象共有的方法,应该添加到构造函数的原型对象中,子对象调用方法时,先在子对象本地查找。如果本地对象没有找到,才延原型链向父级对象查找,直到找到为止。


多态

如果从父对象继承来的方法不好用,可在对象本地定义同名方法,覆盖父对象中的方法(重写)。
强调:原型对象中,方法(函数)里面的this指向由调用该方法的点(.)前的某个子对象(实例)来决定。


原文链接
1、面向对象的三大特征


17、JavaScript的内置对象

1、掘金-原文


26、严格模式

1、掘金-严格模式


27、变量的提升

27.1、定义

变量的提升是JavaScript的默认行为,它会将所有变量声明移动到当前作用域的顶部,并且可以在声明之前使用变量。初始化不会被提升(赋值留在原地,函数除外),提升仅作用于变量的声明。


27.2、原文链接
1、JavaScript之变量提升-笔试题


30、事件捕获和事件冒泡

30.1、定义
30.1.1、事件捕获

事件捕获是从document开始发生,发生顺序一直向内,直到最后一个元素结束。


30.1.2、事件冒泡

事件冒泡是从最内层元素开始发生,发生顺序一直向外,直到document冒泡才结束。


30.2、扩展

1、对于非点击的节点,先执行捕获再执行冒泡。
2、对于被点击的节点,按照顺序执行先注册的事件。


30.3、原文链接
1、事件捕获和事件冒泡


31、设计模式

31.1、单例模式
31.1.1、概念

保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。


31.1.2、适用场景

一个单一对象。比如:弹窗,无论点击多少次,弹窗只应该被创建一次。


31.1.3、代码实现

class CreateUser {
    constructor(name) {
        this.name = name;
        this.getName();
    };

    getName() {
        console.log(this.name);
    };
};

// 代理实现单例模式
let ProxyMode = (function () {
    let instance = null;

    return function (name) {
        if (!instance) instance = new CreateUser(name);

        return instance;
    };
})();

// 测试单体模式的实例
let a = new ProxyMode("aaa");
console.log(a);
// CreateUser {name: "aaa"}

let b = new ProxyMode("bbb");
console.log(b);
// CreateUser {name: "aaa"}

// 因为单体模式是只实例化一次,
// 所以下面的实例是相等的
console.log(a === b);
// true

31.2、策略模式
31.2.1、概念

定义一系列的算法,把他们一个个封装起来,并且使他们可以相互替换。


31.2.2、目的

策略模式的目的就是将算法的使用和算法的实现分离开。


31.2.3、解释

一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类(可变),策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类Context(不变),Context接受客户的请求,随后将请求委托给某一个策略类。要做到这一点,说明Context中要维持对某个策略对象的引用。


31.2.4、代码实现

// 策略类
let levelOBJ = {
	funA: function(money) {
		return money * 5;
	},
	funB: function(money) {
		return money * 3;
	},
	funC: function(money) {
		return money * 2;
	}
};


let calculateBouns = function(level, money) {
	return levelOBJ[level](money);
};

console.log(calculateBouns('funA', 10));
// 50
console.log(calculateBouns('funB', 20));
// 60
console.log(calculateBouns('funC', 30));
// 60

31.3、代理模式
31.3.1、概念

为一个对象提供一个代用品或占位符,以便控制对它的访问。


31.3.2、常用的虚拟代理形式

某一个花销很大的操作,可以通过虚拟代理的方式延迟到这种需要它的时候才去创建(例:使用虚拟代理实现图片懒加载)。


31.3.3、图片懒加载的方式

先通过一张loading图占位,然后通过异步的方式加载图片,等图片加载好了再把完成的图片加载到img标签里面。


31.3.4、解释

使用代理模式实现图片懒加载的优点还有符合单一职责原则。减少一个类或方法的粒度和耦合度。


31.3.5、代码实现

let imgFunc = (function() {
	// 创建一个img标签
	let imgNode = document.createElement('img');

	// 把标签放到body上
	document.body.appendChild(imgNode);

	// 返回setSrc函数
	return {
		setSrc: function(src) {
			imgNode.src = src;
		}
	};
})();

let proxyImage = (function() {
	// 创建一个img标签
	let img = new Image();

	// 给img标签添加自执行函数
	img.onload = function() {
		imgFunc.setSrc(this.src);
	};

	return {
		setSrc: function(src) {
			imgFunc.setSrc('/img/loading02.gif');
			setTimeout(() => {
				img.src = src;
			}, 1000);
		}
	};
})();

console.log(proxyImage);
// {setSrc: ƒ}

proxyImage.setSrc('/img/08.jpg');

31.4、中介者模式
31.4.1、概念

通过一个中介者对象,其他所有的相关对象都通过该中介者对象来通信,而不是相互引用,当其中的一个对象发生改变时,只需要通知中介者对象即可。通过中介者模式可以解除对象与对象之间的紧耦合关系。


31.4.2、例如

现实生活中,航线上的飞机只需要和机场的塔台通信就能确定航线和飞行状态,而不需要和所有飞机通信。同时塔台作为中介者,知道每架飞机的飞行状态,所以可以安排所有飞机的起降和航线安排。


31.4.3、适用的场景

例如购物车需求,存在商品选择表单、颜色选择表单、购买数量表单等,都会触发change事件,那么可以通过中介者来转发处理这些事件,实现各个事件间的解耦,仅仅维护中介者对象即可。


31.4.4、html

<div>
    <div>
        <span>选择颜色: </span>
        <select id="colorSelect">
            <option value="">请选择</option>
            <option value="red">红色</option>
            <option value="blue">蓝色</option>
        </select>
    </div>
    <div>
        <span>选择内存: </span>
        <select id="memorySelect">
            <option value="">请选择</option>
            <option value="32G">32G</option>
            <option value="16G">16G</option>
        </select>
    </div>
    <div>
        <span>输入购买数量: </span>
        <input type="text" id="numberInput" />
    </div>
    <div>
        <span>您选择了颜色: </span>
        <span id="colorInfo"></span>
    </div>
    <div>
        <span>您选择了内存: </span>
        <span id="memoryInfo"></span>
    </div>
    <div>
        <span>您输入了数量: </span>
        <span id="numberInfo"></span>
    </div>
    <div>
        <button id="nextBtn" disabled="true">请选择手机颜色、内存和购买数量</button>
	</div>
</div>

<script src="./index.js"></script>

31.4.5、JavaScript

// 手机库存数据
let goods = { "red|32G": 3, "red|16G": 0, "blue|32G": 1, "blue|16G": 6 };

// 中介者对象
let mediator = (function () {
    let colorSelect = document.getElementById('colorSelect'),
        memorySelect = document.getElementById('memorySelect'),
        numberInput = document.getElementById('numberInput'),
        colorInfo = document.getElementById('colorInfo'),
        memoryInfo = document.getElementById('memoryInfo'),
        numberInfo = document.getElementById('numberInfo'),
        nextBtn = document.getElementById('nextBtn'),
        regNumber = /^[1-9]{1}[0-9]{0,2}$/;

    return {
        changed: function (obj) {
            // 颜色
            let color = colorSelect.value,
                // 内存
                memory = memorySelect.value,
                // 数量
                number = numberInput.value,
                // 颜色和内存对应的手机库存数量
                stock = goods[`${color}|${memory}`];

            // 如果改变的是选择颜色下拉框
            if (obj === colorSelect) {
                colorInfo.innerHTML = color;
            } else if (obj === memorySelect) {
                memoryInfo.innerHTML = memory;
            } else if (obj === numberInput) {
                numberInfo.innerHTML = number;
            }

            if (!color) return (nextBtn.disabled = true, nextBtn.innerHTML = '请选择手机颜色');

            if (!memory) return (nextBtn.disabled = true, nextBtn.innerHTML = '请选择内存大小');

            if (!regNumber.test(number)) return (nextBtn.disabled = true, nextBtn.innerHTML = '请输入正确的购买数量');

            if (number > stock) return (nextBtn.disabled = true, nextBtn.innerHTML = '库存不足');

            nextBtn.disabled = false;
            nextBtn.innerHTML = '放入购物车';
        }
    }
})();

// 事件函数
// id选择器可以直接绑定事件,不需要特意获取。
colorSelect.onchange = function () {
    mediator.changed(this);
};
memorySelect.onchange = function () {
    mediator.changed(this);
};
numberInput.oninput = function () {
    mediator.changed(this);
};

31.5、装饰者模式
31.5.1、概念

在不改变对象自身的基础上,在程序运行期间给对象动态地添加方法。


31.5.2、例如

现有4种型号的自行车分别被定义成一个单独的类,如果给每辆自行车都加上前灯、尾灯、铃铛这3个配件,如果用类继承的方式,需要创建4 * 3 = 12个子类。但如果通过装饰者模式,只需要创建3个类。


31.5.3、适用的场景

原有方法维持不变,在原有方法上再挂载其他方法来满足现有需求;函数的解耦,将函数拆分成多个可复用的函数,再将拆分出来的函数挂载到某个函数上,实现相同的效果但增强了复用性。


31.5.4、用AOP装饰函数实现装饰者模式

Function.prototype.before = function(beforefn) {
	// 保存原函数引用
	let self = this;

	// 返回包含了原函数和新函数的'代理函数'
	return function() {
		// 执行新函数,修正this
		beforefn.apply(this, arguments);
		// 执行原函数
		return self.apply(this, arguments);
	};
};

Function.prototype.after = function(afterfn) {
	let self = this;
	return function() {
		let ret = self.apply(this, arguments);
		afterfn.apply(this, arguments);
		return ret;
	};
};

let func = function() {
	console.log('2');
};

// func1和func3为挂载函数
let func1 = function() {
	console.log('1');
};

let func3 = function() {
	console.log('3');
};

func = func.before(func1).after(func3);
func();
// 1  2  3

31.6、原文链接
1、单例模式
2、策略模式
3、代理模式
4、中介者模式
5、装饰者模式
6、掘金 - 原文


32、promise、async和await

1、promise/async/await
2、掘金-题目


33、垃圾回收机制


34、DOM


35、BOM


36、ajax


37、观察者模式

1、观察者模式
1、CSDN博客-原文
3、掘金-原文


38、发布订阅模式

1、发布订阅模式
2、CSDN 博客 - 原文
3、掘金-原文


41、原型链的prototype和__proto__的区别


------------------------------zd------------------------------


1、回调函数

  1. 回调函数

2、变量提升

  1. 变量提升

3、函数作用域 (scopes) 和函数作用域链

  1. scopes

4、闭包

  1. 闭包

5、统计字符串

  1. 统计字符串

6、this

  1. this - 示例

7、深浅克隆

  1. 深浅克隆

8、构造函数(constructor)

9、包装类型

  1. String - 包装类型

10、内置类

11、重写

12、面向对象

  1. 面向对象的三大特征

13、访问器属性

  1. 访问器属性

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值