慢慢补充中,笔者因为最近比较忙,先整理薄弱点,牛客前端课程的听课笔记,仅做分享,方便学习,侵删。如果对你有帮助,希望能够给笔者一个赞鼓励笔者继续分享,感谢!
ES5
1.数据类型
有 7 种原始类型,1 种引用类型
- Boolean
- null
- undefined
- Number
- BigInt
- String
- Symbol
- Object
2.判断数据类型的几种方法
2.1 typeof
- 对于基本类型,除 null 以外,均可以返回正确的结果
- 对于引用类型,除 function 以外,一律返回 object 类型
- 对于 null,返回 object 类型
- 对于 funciton,返回 function 类型
typeof null === 'object'
const func = () => {}
typeof func === 'function'
2.2 instanceof
检测某一个实例的原型链上是否有这个类的原型属性
- 可以区分复杂数据类型
- 只要在当前实例的原型链上,我们用其检测出来的结果就是 true
- 不能检测基本类型
手写instanceof
function myinstanceof(left, right) {
let proto = left.__proto__;
let prototype = right.prototype;
while (true) {
if (proto == null) return false;
if (proto == prototype) return true;
proto = proto.__proto__;
}
}
const obj = new Object();
console.log(myinstanceof(obj, Object));
2.3 constructor
用于引用类型,不能检测null、undefined,原型链不会干扰
const num = 123;
console.log(num.constructor === Number);
2.4 Object.prototype.toString.call
适用于所有类型的判断检测
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([1, 2, 3])); // [object Array]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call({name: 'Jack'})); // [object Object]
console.log(Object.prototype.toString.call(() => {})); // [object Function]
console.log(Object.prototype.toString.call(new Date())); // [object Date]
console.log(Object.prototype.toString.call(/\d/)); // [object RegExp]
console.log(Object.prototype.toString.call(Symbol('123'))); // [object Symbol]
2.赋值、深浅拷贝
浅拷贝方法:
- Object.assign
- lodash.clone
- 展开运算符 ...
- Array.prototype.concat
- Array.prototype.slice
深拷贝方法:
- JSON.parse(JSON.stringify()),但不能处理函数(null)和正则(空对象),
- lodash.cloneDeep
- jQuery.extend
const $ = require('jquery');
const obj1 = { ... }
const obj2 = $.extend(true, {}, obj1); // ture表示深拷贝
- 手写深拷贝
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== 'object') return obj;
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash):
}
}
return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj;
let d = deepClone(obj);
obj.address.x = 200;
console.log(d):
3.垃圾回收方式
3.1 栈中的垃圾回收方式
当一个函数执行结束之后,JS引擎会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文
3.2 堆中的垃圾回收方式
- 标记
- 回收
- 内存整理
3.2.1 新生区垃圾回收
- 通常为小对象
- 比较频繁
- 通常存储容量在1 - 8M
- 两次垃圾回收后还存活的对象,会被移动到老生区中
- 使用 Scavenge 算法来处理垃圾回收
Scavenge 算法
- 垃圾标记
- 存活对象复制到空闲区域
- 对象区域与空闲区域角色翻转
3.2.2 老生区垃圾回收
特点:
- 占用空间大
- 存活时间长
过程:
- 标记-清除算法,只标记清除不整理,会产生大量不连续的内存碎片
- 标记-整理算法
- 标记
- 整理,将所有存活的对象都向内存的一端移动
- 清除,清除掉端边界以外的内存
3.2.3 全停顿
因为 JS 是运行在主线程之上的,一旦执行垃圾回收算法,JS 的执行就会暂停,待垃圾回收完毕后再恢复脚本执行。这种行为被叫做全停顿。
增量标记: 将标记过程分为一个个子过程,与 JS 应用逻辑交替执行,之后清除、整理。
4.避免内存泄漏的方式
- 尽可能少的创建全局变量
- 手动清除定时器
- 少用闭包
- 清除 DOM 引用
- 使用弱引用类型
5.继承
继承就是一个对象可以访问另外一个对象中的属性和方法,在 JS 中,我们通过原型和原型链的方式来实现了继承特性。
5.1 原型
JS 的每个对象都包含了一个隐藏属性__proto__,即为该对象的原型,该对象可以直接访问其原型对象的方法或属性。
5.2 原型链
查找不是对象的本身属性的路径称为原型链。
5.3 构造函数如何创建对象?
function DogFactory(type, color) {
this.type = type;
this.color = color;
}
const dog = new DogFactory('Dog', 'Black');
// 创建实例的过程
// const dog = {};
// dog.__proto__= DogFactory.prototype;
// DogFactory.call(dog, 'Dog', 'Black');
5.4 继承的方式
5.4.1 原型链继承
原理: 将子类的原型指向父类的实例
优点:
- 父类新增的原型方法或属性,子类都可以访问
- 简单容易实现
缺点
- 不能实现多重继承
- 来自原型对象的所有属性被所有实例共享
- 创建子类实例时,无法向父类构造函数传参
// 父类型
function Person(name, age) {
this.name = name;
this.age = age;
this.play = [1, 2, 3];
this.setName = function() {}
}
Person.prototype.setAge = function() {}
// 子类型
function Student(price) {
this.price = price;
this.setScore = function() {}
}
Student.prototype = new Person('Wang', 23);
// maybe: 缺少修复构造函数
const s1 = new Student(15000);
const s2 = new Student(14000);
console.log(s1, s2);
5.4.2 借用构造函数实现继承
原理: 在子类构造函数中通过call调用父类构造函数
优点:
- 解决了原型链继承中子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多重继承(call多个父类构造函数)
缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承父类原型的属性和方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
function Person(name, age) {
this.name = name;
this.age = age;
this.setName = function() {}
}
Person.prototype.setAge = function() {}
function Student(name, age, price) {
Person.call(this, name, age);
// 相当于:this.Person(name, age);
// this.name = name;
// this.age = age;
// this.setName = function() {}
this.price = price;
}
const s1 = new Student('Tom', 20, 15000);
5.4.3 原型链 + 借用构造函数的组合继承
原理: 通过调用父类构造函数,继承父类属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。
优点:
- 可以继承实例属性和方法,也可以继承原型属性和方法
- 不存在引用属性共享问题
- 可传参
- 父类原型上的函数可复用
缺点:
- 调用了两次父类构造函数,生成了两份实例
function Person(name, age) {
this.name = name;
this.age = age;
this.setAge = function() {}
console.log(1);
}
Person.prototype.setAge = function() {
console.log('111');
}
function Student(name, age, price) {
Person.call(this, name, age);
this.price = price;
this.setScore = function() {}
}
Student.prototype = new Person();
// 组合继承需要修复构造函数指向
Student.prototype.constructor = Student;
const s1 = new Student('Tom', 20, 15000);
const s2 = new Student('Jack', 22, 14000);
console.log(s1, s2);
5.4.4 class 关键字
原理: ES6 中引入了 class 关键字,class 可以通过 extends 实现继承,还可以通过 static 定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
优点:
- 语法简单易懂,操作更方便
缺点:
- 并不是所有的浏览器都支持class关键字
class Person {
// 调用类的构造方法
constructor(name, age) {
this.name = name;
this.age = age;
}
// 定义一般的方法
showName() {
console.log('调用父类的方法');
console.log(this.name, this.age);
}
}
const p1 = new Person('Kobe', 39);
console.log(p1);
// 定义一个子类
class Student extends Person {
constructor(name, age, salary) {
super(name, age); // 通过super调用父类的构造函数
this.salary = salary;
}
showName() {
// 子类定义自己的方法
console.log('调用子类的方法');
console.log(this.name, this.age, this.price);
}
}
const s1 = new Student('Wade', 38, 10000);
console.log(s1);
s1.showName();
扩展阅读,参考JavaScript常用八种继承方案
ES6
介绍一些 ES6 常用的新特性
6. let、const、var
6.1 var 关键字
var 是函数作用域
// 声明的全局变量挂载在window对象下
var a = 1;
console.log(window.a);
// 变量提升
console.log(a);
var a = 1;
// 声明的变量可以重复声明和修改
var a = 1;
var a = '111';
console.log(a);
6.2 let 和 const 关键字
- let 和 const 声明的全局变量不挂载在window对象下
- let 和 const 定义的变量不会出现变量提升现象
- let 和 const 也存在变量声明提升,只是没有初始化分配内存
- 创建一个变量有三个操作,声明(提到作用域顶部)、初始化(赋默认值)、赋值
- 其实就是创建提升,因为没有初始化分配内存,所以使用该变量就会报错,这是暂时性死区
- let 和 const 是 JS 中的块级作用域
- let 和 const 不允许重复声明(会抛出错误)
- const 声明一个只读的常量。一旦声明,常量的值就不能改变(如果声明是一个对象,那么不能改变的是对象的引用地址)
// var 变量提升
console.log(a);
var a = 1;
// const, let 暂时性死区
const b = 1;
{
console.log(b);
const b = 2;
}
if (true) {
var a = 1; // 变量会提升到全局
}
console.log(a);
if (true) {
let b = 1;
}
console.log(b);
const a = 1;
a = 2;
6.3 解构符号
6.3.1 基本使用
let [a, b, c] = [1, 2, 3];
console.log(a, b, c);
6.3.2 嵌套使用
// 数组
let [a, [[b], c] = [1, [[2], 3]];
console.log(a, b, c);
// 对象
let obj = {p: ['hello', {y: 'world'}]};
let {p: [x, {y}]} = obj;
console.log(x, y);
6.3.3 忽略
// 数组
let [a, , b] = [1, 2, 3];
console.log(a, b);
// 对象
let obj = {p: ['hello', {y: 'world'}]};
let {p: [x, {}]} = obj;
console.log(x);
6.3.4 不完全解构
// 数组
let [a = 1, b] = [];
console.log(a, b);
// 对象
let obj = {p: [{y: 'world'}]};
let {p: [{y}, x]} = obj;
console.log(x, y);
6.3.5 剩余运算符
// 数组
let [a, ...b] = [1, 2, 3];
console.log(a, b);
// 对象
let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40};
console.log(a, b, rest);
6.3.6 字符串
let [a, b, c, d, e] = 'hello'
console.log(a, b, c, d, e);
6.3.7 解构默认值
// 当匹配结果为undefined时,会触发默认值作为返回结果
let [a = 2] = [undefined];
console.log(a);
let {a = 10, b = 5} = {a: 3};
console.log(a, b);
6.3.8 交换变量的值
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b);
6.3.9 解构赋值的应用
// 1.浅克隆与合并
const name = {name: 'aaa'};
const age = {age: 'bbb'};
const person = {...name, ...age};
console.log(person);
// 数组
const a = [1, 2, 3];
const b = [4, 5];
const c = [...a, ...b];
console.log(c);
// 2.提取JSON数据
const jsonData = {id: 10, status: 'OK', data: [111, 222]};
const {id, status, data: numbers} = jsonData;
console.log(id, status, numbers);
// 3.函数参数的定义
// 参数有序
function func1([a, b, c]) { console.log(a, b, c); }
func1([1, 2, 3]);
// 参数无序
function func2({x, y, c}) { console.log(a, b, c); }
func2({z: 3, y: 2, x: 1});
// 参数有默认值
function func3([a = 1, b]) { console.log(a, b); }
func3([, 3]);
6.4 箭头函数
1. 箭头函数没有 this,this 是从外部获取的
const group = {
title: 'Our Group',
students: ['John', 'Pete', 'Alice'],
showList() {
this.students.forEach(name => alert(this.title + ': ' + name));
}
}
group.showList();
2. 不能对箭头函数进行 new 操作
不具有 this 自然也就意味着另一个限制:箭头函数不能用作构造器(不能用 new 调用它们)
const Foo = () => {}
const foo = new Foo(); // TypeError: Foo is not a constructor
3. 箭头函数没有arguments
箭头函数没有自己的 arguments 对象,但是箭头函数可以访问外围函数的 arguments 对象
function constant() {
return () => arguments[0];
}
const result = constant(1);
console.log(result());
4. 箭头函数没有原型
箭头函数没有原型,在其内使用 this、arguments、new.target,由外围最近一层非箭头函数决定.
// 箭头函数没有原型
const Foo = () => {};
console.log(Foo.prototype); // undefined
// 箭头函数不能 new
const Foo = () => {};
const foo = new Foo(); // TypeError
// 箭头函数没有this
/*const group = {
title: 'Our Group',
students: ['John', 'Pete', 'Alice'],
showList() {
this.students.forEach(function(stu) {
alert(this.title + ': ' + stu);
});
}
};*/
const group = {
title: 'Our Group',
students: ['John', 'Pete', 'Alice'],
showList() {
this.students.forEach(stu => alert(this.title + ': ' + stu));
}
};
group.showList();
// 箭头函数没有 arguments
function constant() {
return () => arguments[0];
}
const result = constant(1);
console.log(result());
6.5 Map 与 WeakMap
6.5.1 Map 特点
- Map 的键和值可以是任何数据类型
- 键值对按照插入顺序排列
- Map 数据可迭代,Object 数据不可迭代
// Map 的键和值可以是任何数据类型
const mp = new Map();
const o = { num: 1 };
mp.set(o, '123');
console.log(mp);
// Map 键值对按照插入顺序排列
const mp = new Map([[1, '111'], [2, '222']]);
console.log(mp);
// Object 自动排序
const obj = {'2': '111', '1': '222'};
console.log(obj);
// Map 数据可迭代,Object 数据不可迭代
const map = new Map([[1, '111'], [2, '222']]);
console.log(map);
for (const [key, value] of map) console.log(key, value);
const obj = {'2': '111', '1': '222'};
console.log(obj);
for (const [key, value] of obj) console.log(key, value);
6.5.2 Map 的常用属性
// api
map.set(key, value);
map.get(key);
map.delete(key);
map.clear();
map.entries();
map.has(key);
map.keys();
map.values();
map.size();
// 遍历
const map = new Map([['a', 1], ['b', 2]]);
for (const key of map.keys()) console.log(key);
for (const value of map.values()) console.log(value);
for (const item of map.entries()) console.log(item);
// 等价于 for-of
for (const [key, value] of map.entries()) console.log(key, value);
6.5.3 WeakMap 特点
- 只接受对象作为键名(null 除外),不接受其他类型
- 键值可以是任意的
- 键名是弱引用,键名所指向的对象可以被垃圾回收,此时键名是无效的
- 不可枚举,方法有 get、set、has、delete
// 只能使用对象作为键名
const weakMap = new WeakMap();
weakMap.set('a', 111); // Error
// 键名是弱引用,如果对象销毁,该属性就销毁
const weakMap = new WeakMap();
let o = { num: 1 };
weakMap.set(o, 111);
o = null;
console.log(weakMap);
// 不能遍历
const weakMap = new WeakMap();
let o = { num: 1 };
weakMap.set(o, 111);
for (const item of weakMap) console.log(item); // Error