MENU
- 01、JavaScript数据类型及存储方式
- 02、var、let和const的区别
- 03、null和undefined的区别
- 04、防抖debounce
- 05、节流throttle
- 06、克隆、拷贝
- 07、作用域和作用域链
- 08、闭包
- 09、原型和原型链
- 10、new操作符
- 11、两等(==)和非(!)的优先级比较
- 12、数据类型转换
- 13、检测数据类型的方法
- 14、检测一个对象是不是数组
- 15、this
- 16、ES6及往后版本的新特性
- 17、箭头函数与普通函数有什么区别
- 18、字符串常用方法
- 19、数组常用方法
- 20、indexOf和findIndex的区别
- 21、双等与三等有什么区别
- 22、事件委托、代理
- 23、JavaScript之继承
- 18、图片懒加载
- 20、JavaScript实现内置方法
- 21、函数柯理化
- 22、任务(事件)队列和事件循环
- 23、正则表达式
- 24、实现浏览器内多个标签页(.html 页面)之间的通信
- 25、面向对象(OOP)
- 17、JavaScript的内置对象
- 26、严格模式
- 27、变量的提升
- 30、事件捕获和事件冒泡
- 31、设计模式
- 32、promise、async和await
- 33、垃圾回收机制
- 34、DOM
- 35、BOM
- 36、ajax
- 37、观察者模式
- 38、发布订阅模式
- 41、原型链的prototype和__proto__的区别
- ------------------------------zd------------------------------
- 1、回调函数
- 2、变量提升
- 3、函数作用域 (scopes) 和函数作用域链
- 4、闭包
- 5、统计字符串
- 6、this
- 7、深浅克隆
- 8、构造函数(constructor)
- 9、包装类型
- 10、内置类
- 11、重写
- 12、面向对象
- 13、访问器属性
01、JavaScript数据类型及存储方式
数据类型
JavaScript共有8种数据类型。
7种基本数据类型:Null
、Undefined
、Boolean
、Number
、String
、Symbol
(ES6新增,表示独一无二的值)和BigInt
(ES10新增)。
1种引用数据类型:Object。Object里面包含Object
、Array
、Function
、Date
、RegExp
等。
总结: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不可以。
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,就可以继续执行 。
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
指向改变,方法有三:apply
、call
、bind
。
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、
null
和undefined
没有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()
据说是无敌万能的方法,如果对象是
null
或undefined
,直接返回'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、call
和apply
会调用函数,并且改变函数内部this
指向。
2、call
和apply
传递的参数不一样,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.call
、apply
、bind
中的this
被强行绑定在指定的那个对象上。
5. 箭头函数中的this
比较特殊,箭头函数的this
指向父作用域中的this
,不是调用时的this
。要知道前四种方式,都是调用时确定,也就是动态的this
。而箭头函数是静态的this
,声明的时候就确定了。
6.call
、apply
、bind
都是函数内置的一些API
,调用时可以为函数指定this
的执行,同时也可以传参。
原文链接
1、6种函数调用方式的this指向
2、改变this指向之call/apply/bind
3、this总结-06
4、this带图
16、ES6及往后版本的新特性
序号 | 关键字 | 功能 |
---|---|---|
1 | 序号 | let |
2 | const | 定义常量 |
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 |
9 | class类的定义 | class Point { constructor(x, y) { this.x = x; this.y = y; } } |
17、箭头函数与普通函数有什么区别
18、字符串常用方法
序号 | 方法 | 说明 | result |
---|---|---|---|
1 | slice | 下标截取字符串 | 此方法传入的参数是字符串下标,第一个参数要比第二个参数小,否则会返回不确定的值,截取的值包含第一个参数,不包含第二个参数。 |
2 | substr | 下标和长度截取字符串 | 此方法传入下标和长度,第一个参数传入下标,截取的值包含下包,第二个参数是截取长度 |
3 | substring | 下标截取字符串 | 此方法传入的参数是字符串下标,两个参数不区分大小,因为此方法会自动找到最小下标开始截取到结束下标,截取的值不包含结束下标 |
4 | split | 字符截取字符串 | 根据传入的参数对字符串进行分割,并且返回数组 |
5 | replace | 替换字符串内容 | 用另一个值替换在字符串中指定的值 |
19、数组常用方法
序号 | API | 功能描述 | 是否改变原数组 |
---|---|---|---|
01 | unshift | 此方法是将一个或多个元素添加到数组的开头,并返回新数组的长度 | Y |
02 | push | 此方法是在数组的后面添加新加元素,此方法改变了数组的长度 | Y |
03 | shift | 此方法删除数组第一个元素,并返回数组,此方法改变了数组的长度 | Y |
04 | pop | 此方法删除数组最后一个元素,并返回数组,此方法改变了数组的长度 | Y |
05 | join | 此方法将数组转为字符串并返回转化的字符串数据,不会改变原来的数组 | N |
06 | splice | Array.splice(开始位置, 删除的个数,元素) ,万能方法,可以实现增删改 | Y |
07 | slice | 此方法截取指定位置的数组,并且返回截取的数组,不会改变原数组 | N |
08 | map | 此方法是将数组中的每个元素调用一个提供的函数,结果作为一个新的数组返回,并没有改变原来的数组 | N |
09 | forEach | 此方法是将数组中的每个元素执行传进提供的函数,没有返回值,注意和map方法区分 | N |
10 | filter | 此方法是将所有元素进行判断,将满足条件的元素作为一个新的数组返回 | 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的相同点
indexOf
与findIndex
都是查找数组中满足条件的第一个元素的索引(下标)。
21、双等与三等有什么区别
22、事件委托、代理
1、多种不同的定义
1、通俗的讲,事件委托就是把一个元素响应事件(click、keydown等)的函数委托到外层元素上。
2、简单的讲,事件委托就是利用JavaScript事件冒泡的特性,将内层元素的事件委托(绑定)给外层元素处理。
3、一般来讲,事件委托会把一个或者一组元素的事件委托(绑定)到它的父层元素或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。
2、原文链接
1、普通事件和事件委托
23、JavaScript之继承
1、原型直接继承
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
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
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的内置对象
26、严格模式
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
33、垃圾回收机制
34、DOM
35、BOM
36、ajax
37、观察者模式
38、发布订阅模式
1、发布订阅模式
2、CSDN 博客 - 原文
3、掘金-原文