Javascript基础知识整理—1

2023-05-16

1. JS数据类型

原始数据类型: boolean,string,null,undefined,symbol,bigint,number
引用类型: Object,Array,RegExp(正则),Date,Math,Function
(直接修改引用类型上的属性值,依旧指向的是原先的引用,而赋值新的对象后,相当于指向了另外的引用)

2. null不是对象

typeof null = Object 原因在于其二进制的前三位都为0,所以才会判定null为object

3. ‘‘1’’.toString()为什么可以调用

体现了基本包装类型的性质,当我们对一个保存了基本数据类型值的变量调用方法时,都会创建一个基本包装类型的对象。

// "1".toString
var obj = new Object('1'); // 实际上创建的结果是 String {"1"},也可以理解为new String("1")

obj.toString();
obj = null;  //使用完后会立马销毁对象,清理其占用的内存
4. 0.1+0.2为什么不等于0.3

原因在于0.1和0.2在转换成二进制后无限循环,故相加后出现精度缺失,相加后因浮点数小数位的限制而截断的二进制数字在转换为十进制就会变成0.30000000000000004

5. typeof 能否正确判断类型

对于基本数据类型,除了null以外都可以,输出对应的基本数据类型
对于引用数据类型,除了function以外都会输出object

typeof 1;  // 'number'
typeof null // 'object'
6. instanceof 能否正确判断基本数据类型(检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上)

能,相当于自定义instanceof
Symbol.hasInstance用于判断某对象是否为某构造器的实例。因此你可以用它自定义 instanceof 操作符在某个类上的行为。

class primitenNumber {
	static [Symbol.hasInstance](instance) {
		return typeof instance === 'number';
	}
}

console.log('111' instanceof primitenNumber) // true

7. 手动实现instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

// type也可以是构造函数
function instanceof(value, type) {
	// 如果是基本数据类型,则直接返回
	if (typeof value !== 'object' || value === null) return false;
	// getPrototypeOf返回指定对象的原型,即__proto__
	let proto = Object.getPrototypeOf(value)
	while(true) {
		// 查到尽头还未查到
		if (proto === null) return false;
		// 找到相同的原型对象
		if (proto === type.prototype) return true;
		proto = Object.Object.getPrototypeOf(proto);
	}
}

console.log(instanceof('111', String)) // false
console.log(instanceof(new String('111'), String)) // true

8. prototype、proto(该属性在ES标准定义中的名字应该是[[Prototype]])与constructor

链接地址

__proto__:double underscore proto,该属性在ES标准中应该叫做[[Prototype]]

  1. 首先需要明确的一点是:__proto__,constructor是对象独有的,prototype是函数独有的,但是在JS中,函数实际上也是一种对象,所以函数也存在__proto__和constructor。
    在这里插入图片描述
  2. __proto__的作用就是当访问一个对象的属性时,如果该对象内部没有对应属性,那么就会去对象的__proto__属性指向的对象里面找(可以理解为其父对象),如果没找到,就会到其父对象的__proto__属性指向的对象里面找,最终会找到null,再往上会报错,一般判定null为原型链的尽头。
    其实我们平时调用的字符串方法、数组方法、对象方法、函数方法等都是靠__proto__继承而来的。

prototype: prototype是函数所独有的

function Foo(){};

const f1 = new Foo();
  1. 它是从一个函数指向一个对象,是函数的原型对象,也就是函数所创建的实例对象的原型
  2. f1.__proto__ = Foo.prototype
  3. 它的作用就是让该函数实例化的所有对象都可以找到共用的属性和方法,任何函数在创建的时候,其实会默认同时创建该函数的prototype对象。

constructor

  1. constructor也是对象才有的,它是从一个对象指向一个函数,含义是指向该对象的构造函数
  2. 每个对象都有构造函数:这里的意思是每个对象都可以找到其对应的constructor,因为创建对象的前提是需要有constructor,而这个constructor可能是对象自己本身显式定义的或者通过__proto__在原型链中找到的。而单从constructor这个属性来讲,只有prototype对象才有。
8.1 new操作符
  1. 创建一个空对象 obj{}。
  2. 将obj的[[Prototype]]指向构造函数constructor(即我们希望创建实例的目标对象)的原型(obj.proto === constructor.prototype)。
  3. 将构造函数constructor内部的this绑定到obj上。
  4. 若构造函数没有返回非原始值(即 return this),则返回新建对象obj(即默认添加return this),反之返回其返回值。

这里补充说明一下:[[prototype]]属性是隐藏的,不过目前大部分新浏览器实现方式是使用__proto__来表示。构造函数的prototype属性我们是可以显式访问的。

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

const car1 = new Car('Eagle', 'Talon TSi', 1993);


function new(constru, ...args){
	if (typeof constru !== 'function' || constru === null) return;
	// Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。
	const obj = Object.create(constru.prototype);
	// 将constru内部的this指向obj
	const result = constru.apply(obj, ...args);
	
	// 如果构造函数返回的是对象,则使用构造函数执行的结果。否则,返回新创建的对象
	return result instaceof Object ? result : obj;
}

new(Car, 'Eagle', 'Talon TSi', 1993);
9. Object.is和===的区别

Object.is修复了一些 === 判断的缺陷,+0和-0(===将+0和-0视为相等),NAN和NAN(===将其视为不相等)的判断

function is(x, y){
	if (x === y) {
		//运行到1/x === 1/y的时候x和y都为0,但是1/+0 = +Infinity, 1/-0 = -Infinity, 是不一样的 
		// 1/+0 === 1/-0 结果为false
		return x !== 0 || y !== 0 || 1/x === 1/y;
	} else {
	   //NaN===NaN是false,这是不对的,我们在这里做一个拦截,x !== x,那么一定是 NaN, y 同理  
	   //两个都是NaN的时候返回true
		return x !== x && y !== y;
	}

}
10. [] == ![]结果是什么

如果有布尔值那么两边转化为数值进行比较,如果有字符串,那么两边都转化为字符串进行比较
左边会首先转化为number(数组属于引用类型,且是空数组,故会被转化为0,如果是[1],会被转化为1),0
右边会首先转化为boolean值,由于[]作为一个引用类型转换为布尔值为true,!true则为false,再转化为number则为0
0 == 0 返回 true

(1)如果是布尔值,true和false分别被转换为1和0
(2)如果是数字值,返回本身。
(3)如果是null,返回0.
(4)如果是undefined,返回NaN。
(5)如果是字符串,遵循以下规则:
1、如果字符串中只包含数字,则将其转换为十进制(忽略前导0)
2、如果字符串中包含有效的浮点格式,将其转换为浮点数值(忽略前导0)
3、如果是空字符串,将其转换为0
4、如果字符串中包含非以上格式,则将其转换为NaN
(6)如果是对象,则调用对象的valueOf()方法,然后依据前面的规则转换返回的值。如果转换的结果是NaN,则调用对象的toString()方法,再次依照前面的规则转换返回的字符串值。

11. 基本数据类型类型之间的运算
// 字符串和数字相加
'111' + 1 = '1111';

// boolean和数字相加
true + 1 = 2;
false + 1 = 1;

// boolean和字符串相加
false + '1' = 'false1'

1 + null = 1;
1 + undefiend = NaN;
'1' = undefined = '1undefiend'; // null同理

true + undefined = NaN;
true + null = 1;

12. 闭包

闭包是指有权访问另外一个函数作用域中的变量的函数,
当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这就是作用域链

比如回调函数,单例模式也可以使用闭包来实现,高阶函数

var a = 1;
function f1(){
	var a = 2;
	function f2(){
		var a = 3;
		console.log(a); // 3
	}
}

解决闭包的this指向问题
var name = "window" //这里是全局的
var obj = {
    name:"obj",//这里是局部的
    say:function(){  //这里是对象中的方法,这里的this指向obj这个对象
        return function(){  //这里是闭包
            return this.name  //这里的this指向是window,所以结果是window
        }
    }
}
alert(obj.say().call(obj))


var name = "window" //这里是全局的
var obj = {
    name:"obj",//这里是局部的
    say:function(){  //这里是对象中的方法,这里的this指向obj这个对象
        let that = this
        return function(){  //这里是闭包
            return that.name  //这里的this指向是window,所以结果是window
        }
    }
}
alert(obj.say())

使用场景:回调函数(事件,计时器)

解决循环输出的问题:

for (var i = 0; i < 5; i++) {
	setTimeout((index) => {
		console.log(index)
	}, 0, i)
}	

添加setTimeout的第三个参数,作为回调函数的第一个参数
使用let增加块级作用域
立即执行函数

for (var i = 0; i < 5; i++) {
  ((index) => {
     setTimeout(() => {
	   console.log(index)
   }, 0)
  })(i);
}
13. this指向问题
// 对象中this指向问题
let value = '2';
const obj = {
  	value: '1',
	a: function(){
    	console.log(this.value)
    	return function() {
			console.log(this);
		}
    }

}

obj.a()() // '1';
// 如果obj中没有value,则this.value = undefined


// 函数当中this指向问题
const value = 'test';
function con() {
	console.log(this.value);
}

con() // undefined

// 这种情况下this指向undefined或全局,必须要使用new运算符实例化之后,this才能指向正确
function Obj() {
	this.value = 1;
}

Obj.prototype.getValue = () => {
	console.log(this.value);
}

const test = Obj.prototype.getValue;
test()

箭头函数
指向最近的非箭头函数的this

DOM时间绑定
onclick和addEventerListener中 this 默认指向绑定事件的元素。
IE比较奇异,使用attachEvent,里面的this默认指向window。

14. 原型链

在这里插入图片描述

没定义一个函数数据类型,都会自带一个prototype属性,指向函数的原型对象。
当函数经过new运算符调用时,函数就成为了构造函数,返回一个全新的实例对象,这个实例对象有一个proto属性,指向构造函数的原型对象。

15. JS如何实现继承(上结束)
// 通过call方法来实现继承
// 缺点:可以继承父类自己的属性和方法,但是无法继承父类原型上的属性和方法
fucntion parent(){
	this.a = 'name';
  	this.b = function(){
    	console.log('list')
    }
}

function child(){
	parent.call(this);
  	this.c = 'abound';
}

const test = new child();

test.b(); // Unexpected identifier
// 借助原型链
// 缺点:共用相同的原型属性,其中一个实例对象的属性改变,其他实例上对应的属性值也会改变(只针对引用类型,因为引用类型指向的是引用而不是真实的值,基本数据类型不会受到影响)
function parent(){
	this.name = 'xiaohong';
	this.arr = [1, 2, 3, 4];
}

function child() {
	this.type = 'proto';
}

child.prototype = new parent();

const test_1 = new child();
const test_2 = new child();

test_1.arr.push(5);
console.log(test_2.arr); // [1, 2, 3, 4, 5]

// 寄生组合继承(推荐)
function parent(){
	this.name = 'xiaohong';
	this.arr = [1, 2, 3, 4];
}

function child(){
	parent.call(this);
	this.type = 'proto'
}

child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;

补充一点(这里需要更详细的作为补充知识):
在目前实现的继承上其实没有考虑父类的静态方法的继承
附加原型链

//可以使用
Object.setPrototypeOf() 或者 child.__proto__ = parent来实现

继承的最大问题在于:无法决定继承哪些属性,所有属性都得继承。

16. 函数的arguments为什么不是数组?如何转化成数组?

可以理解为arguments是一个类数组
通过扩展运算符或者Array.prototype.slice.call()可以转化为真实的数组

18. JS循环中的中断、异步for…in、forEach、 map

链接地址

第42篇

forEach()

  1. 对于forEach()而言,return是不能够跳出循环的
  2. forEach()的异步处理
async function test(){
	let arr = [4, 2, 1];
	arr.forEach(async (item) => {
		const res = await handle(item);
		console.log(res)
	})
	console.log('end')
} 

function handle(item) {
	return new Promise((res, rej) => {
		setTimeout(() => {
			resolve(item);
		}, 1000 * item)
	})
}

// 期待结果
4
2
1
end

// 实际结果
end
1
2
4

原因在于forEach对于回调函数的处理是拿来即用,所以传入的异步回调还是会按照事件循环机制运转
// forEach核心逻辑
for (var i = 0; i< length; i++) {
	// in可以判断索引值是否在数组中
	if (i in array) {
		var element = array[i];
		useCallback(element, i);
	}
}

对于循环中的异步处理,可以替换为for…of来实现同步执行

// in运算符获取的是数组的下标,of获取的是数组元素
const arr = [4, 3, 2, 1];

async function test() {
	for (const item of arr) {
		const res = await handle(item);
		console.log(res);
	}
	
	console.log('end');
}

for…of之所以可以实现异步函数的同步运行,原因在于它使用了迭代器来进行遍历而不是像forEach一样只是简单地循环。

具有迭代器属性的的数据类型:
如数组、类数组(如arguments、NodeList)、Set和Map。

多个任务成功地按顺序执行!其实刚刚的for...of循环代码就是这段代码的语法糖。
async function test(){
	let arr = [4, 3, 2, 1];
	let interator = arr[Symbol.iterator]();
	let res = interator.next();
	while(!res.done) {
		let value = res.value;
		console.log(value);
		await handle(value);
		res = interator.next();
	}
	console.log('end');
}

19. JS判断数组中是否包含某个值
// indexOf()
此方法判断数组中是否存在某个值,如果存在,则返回数组元素的下标,否则返回-1// includes()
此方法判断数组中是否存在某个值,如果存在返回true,否则返回false

// find(callback)
返回数组中满足条件的第一个元素的值,如果没有,返回undefined

//findeIndex(callback)
返回数组中满足条件的第一个元素的下标,如果没有找到,返回 -1
20. 数组扁平化

链接地址

  1. reduce()方法迭代
let arr = [1,[2,[3,4,5]]];

function flat(arr) {
	// reduce(pre, current):pre表示上一次处理的结果,current表示当前正在处理的值
	return arr.reduce((pre, current) => {
		return pre.concat(Array.isArray(current) ? flat(current) : current)
	})
}
  1. 扩展运算符
concat()方法会去除掉一层数组
let arr = [1,[2,[3,4,5]]];

function flat(arr) {
	while(arr.some((item) => Array.isArray(item))) {
		arr = [].concat(...arr)
	}

	return arr;
}
  1. es6中的flat()
arr.flat([depth])

let arr = [1, [2, [3, 4]]];

function flatten(arr) {
  return arr.flat(Infinity);
}

console.log(flatten(arr));//[ 1, 2, 3, 4 ]

22. bind,call,apply函数(中,18篇)(此处还需要详细看)
bind()方法如果传的是null或者undefined,则this默认指向window
Function.prototype.bind(this, ...args)
返回一个原函数的拷贝,并拥有指定的 this 值和初始参数
如果给bind()的第一个参数传递为nullundefined或空:this在非严格模式下指向全局,严格模式下指向undefined

且bind()中传入的参数会作为绑定函数的参数一次往后排列,bind()中传入的参数排在调用函数时传入的参数前面


对于普通函数,绑定this指向
对于构造函数,要保证原函数的原型对象上的属性不能丢失
//bind返回的函数如果作为构造函数,搭配new关键字出现的话,我们绑定的this就需要被忽略
//处理构造函数场景下的兼容
Function.prototype.bind = Function.prototype.bind || function(context){
	//确保调用bind方法的一定要是一个函数
	if(typeof this !== "function"){
		throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
	}
	var args = Array.prototype.slice.call(arguments, 1);
	var self = this;
	var F = function(){};
	F.prototype = this.prototype;
	var bound = function(){
		var innerArgs = Array.prototype.slice.call(arguments);
		var finalArgs = args.concat(innerArgs);
		//bind返回的函数如果作为构造函数,搭配new关键字出现的话,我们绑定的this就需要被忽略
		self.apply(this instanceof self ? this : context, finalArgs);
	}
	bound.prototype = new F();
	return bound;
}


就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组

call()

// 使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined
Function.prototype.call = function(context) {
	var context = context || window;
	context.fn = this;
	var args = [];
	for (let i = 1; i< arguments.length; i++){
		args.push(arguments[i]);
	}

	var result = context.fn(args);

	delete context;
	return result;
}

apply()
传入的数组将作为一个整体独立参数

Function.prototype.apply = function(context, ...args) {
	var context = context || window;
	context.fn = this;

	var result = context.fn(...args);

	delete context;
	return result;
}

bind和call,apply的区别:

  1. bind返回的是一个拷贝的新函数,故不会立即执行,而apply,call返回的是函数执行的结果
24.浅拷贝

只能拷贝一层对象,对于嵌套的对象而言,仅仅拷贝的是对象的引用
object.assign()
方法将所有可枚举(Object.propertyIsEnumerable() 返回 true)和自有(Object.hasOwnProperty() 返回 true)属性从一个或多个源对象复制到目标对象,返回修改后的对象。

25. 深拷贝(中结束)
// 简易版以及问题
JSON.parse(JSON.stringify())

// 简易版二
function deepClone(obj) {
	let result = Array.isArray(obj) ? [] : {};
	if (obj && typeof obj === 'object') {
		for(let key in obj) {
			if (obj.hasOwnPrototype(key)) {
				if (typeof obj[key] === 'object') {
					result[key] = deepClone(obj[key])
				} else {
					result[key] = obj[key]
				}
			}
		}
	}

	return result;
}


1. 无法解决循环引用的问题
const a = {val: 1};
a.target = a;

const b = JSON.parse(JSON.stringify(a));
//Converting circular structure to JSON

2. 无法拷贝一些特殊函数
如RegExp, Date, Set, Map

3.首先实现一个简易的深拷贝
function deepClone(target) {
	if (typeof target === 'object' && target !== null) {
		const cloneTarget = Array.isArray(target) ? [] : {};
		for (let item in target) {
			if (target.hasOwnProperty(item)) {
				cloneTarget[item] = deepClone(item);
			}
		}
	} else {
		return target;
	}
	
}

4. 解决循环引用的问题
// 使用Map结构来记录,遇到已经创建过的,直接返回
function deepClone(target) {
	const map = new Map();
	if (map.get(target)) {
		return target
	}
	
	if (typeof target === 'object' || typeof target === 'function' && target !== null) {
		map.set(target, true);
		const cloneTarget = Array.isArray(target) ? [] : {};
		for (let item in target) {
			if (target.hasOwnProperty(item)) {
				cloneTarget[item] = deepClone(item);
			}
		}
	} else {
		return target;
	}
	
}
26. js数据存储机制

基本数据类型:栈存储
引用数据类型:在栈中存放对象的引用地址,而真实数据存放在堆中
对于系统栈来说,它的功能除了保存变量之外,还有创建并切换函数执行上下文的功能。

27. 垃圾回收机制

对于JS来说,V8引擎只能使用系统的一小部分内存,具体来说。64位系统只能分配1.4G,32位系统只能分配0.7G。

  1. V8将堆内存分为了新生代和老生带内存分别执行对应的垃圾回收策略
    新生代:临时分配的内存,存活时间短
2. 首先将新生代内存区域分为From(存放正在使用的对象)和To(闲置空间)
3. 检查From中,如果是存活对象,则按照从头放置的顺序转入To中,如果是非存活对象,则回收
4. 转移完毕后,再将两空间对调,即To变为了From,以此为循环
 因为堆是连续分配内存空间,这么做的目的是为了防止在存活对象在占用空间时产生的内存碎片,导致大一点内存的无法被分配空间
 4. 新生代的垃圾回收算法成为Scavenge算法,优势是将内存空间规整之后,方便后续的内存分配,缺点是空间使用率会大大降低。

老生代:常驻内存,存活时间长(由新生代内存空间中多次回收后依然存在的变量会被提升到老生代内存空间中)
新生代内存中的变量如果被多次回收之后依旧存在,则会被放入老生代内存当中

1. 标记—清除
遍历堆中的所有对象进行标记
对于使用变量以及强引用的变量取消标记,剩下的就是需要删除的变量
当然这又会引发内存碎片的问题,存活对象的空间不连续对后续的空间分配造成障碍。老生代又是如何处理这个问题的呢?

2. 整理内存碎片
V8 的解决方式非常简单粗暴,在清除阶段结束后,把存活的对象全部往一端靠拢。

由于JS单线程执行的缘故,V8在执行垃圾回收时不可避免会存在阻塞的情况,那这个时候为了避免这样问题,V8 采取了增量标记的方案,即将一口气完成的标记任务分为很多小的部分完成,每做完一个小的部分就"歇"一下,就js应用逻辑执行一会儿,然后再执行下面的部分,如果循环,直到标记阶段完成才进入内存碎片的整理上面来。其实这个过程跟React Fiber的思路有点像,这里就不展开了。


28. V8执行一段JS代码的过程

机器是读不懂 JS 代码,机器只能理解特定的机器码,那如果要让 JS 的逻辑在机器上运行起来,就必须将 JS 的代码翻译成机器码,然后让机器识别。

  1. 生成AST(抽象语法树) —分为词法分析和语法分析
    词法分析:即分词,将一行行代码分解成一个个token
let       name    =   'xiaoming'
(关键字) (变量名) (赋值) (字符串)

当生成了 AST 之后,编译器/解释器后续的工作都要依靠 AST 而不是源代码。
babel实际上就是将ES6转化为ES6的AST,然后再将ES6的AST转化为ES5的AST,最后再转化为ES5代码。

  1. 生成字节码
    在原先的V8引擎中是直接将AST转化为机器码的,但是体积由于过大的缘故,后面改为先转为字节码之后再一句一句转化为机器码。
    字节码仍然需要转换为机器码,但和原来不同的是,现在不用一次性将全部的字节码都转换成机器码,而是通过解释器来逐行执行字节码,省去了生成二进制文件的操作,这样就大大降低了内存的压力。

  2. 执行代码
    执行字节码的过程中,对于重复代码,会被V8标记为热点代码,通过V8的编译器会将字节码编译成机器码,重复的热点代码会被保存起来,这样就可以在后续遇到重复代码的时候直接复用机器码。

29. AST抽象语法树

https://www.jianshu.com/p/32db2f258986
https://zhuanlan.zhihu.com/p/361683562

  1. 用途
    IDE的错误提示、代码格式化、代码高亮、代码自动补全等
    JSLint、JSHint对代码错误或风格的检查等
    webpack、rollup进行代码打包等
    CoffeeScript、TypeScript、JSX等转化为原生Javascript

  2. JS Parser(JS语言解析器)
    将 JS 解析为 AST
    常用的Parser有esprima、traceur、acorn、shift

  3. 解析过程主要有两个步骤:分词和语法解析
    分词:将代码分解成最小语法单元(具备实际意义的最小单元)数组,通过词法分析将其转化为令牌流的
    语法解析:在分词基础上建立分析语法单元之间的关系

 |  let  |    sum   |       =      |     10     |      +     |   66   |     ;   |
 [{
    "type": "VariableDeclaration",
    "content": {
        {"type": "kind", value: "let"} // kind 表示是什么类型的声明
        {"type": "Identifier", value: "sum"} // Identifier 表示是标识符
        {"type": "init", value: "="} // init 表示初始值的表达式
        {"type": "Literal", value: "10"} // Literal 表示是一个字面量
        {"type": "operator", value: "+"} // operator 表示是一个二元运算符
        {"type": "Literal", value: "66""}
        {"type": "Punctuator", value: ";"}
     }
 }]
  1. 语法单元的分类:
    关键字:例如 var、let、const等
    标识符:没有被引号括起来的连续字符,可能是一个变量,也可能是 if、else 这些关键字,又或者是 true、false 这些内置常量
    运算符: +、-、 *、/ 等
    数字:像十六进制,十进制,八进制以及科学表达式等语法
    字符串:因为对计算机而言,字符串的内容会参与计算或显示
    空格:连续的空格,换行,缩进等
    注释:行注释或块注释都是一个不可拆分的最小语法单元
    其他:大括号、小括号、分号、冒号等

  2. AST作用
    以babel为例,其作用就是通过将ES6的代码转化为AST,再转化为ES5的AST后变成ES5的代码。
    babel的两个核心库:@babel/core用于实现核心引擎转换,babel-types用来进行类型判断,生成AST的零部件

const sum = (a, b) => a + b;

const sum = function(a, b) {
	return a + b;
}

通过 babel的transform来实现
在这里插入图片描述

//transform方法转换code
//babel先将代码转换成ast,然后进行遍历,最后输出code

//babel 核心库,用来实现核心转换引擎
const babel = require('@babel/core')
//类型判断,生成AST零部件
const types = require('babel-types')


let result = babel.transform(code,{
    plugins:[
        {
            visitor
        }
    ]
})

一种是单一处理,结构如下,其中 path 代表当前遍历的路径 path.node 代表当前变量的节点
let visitor = {
	// 节点对应的访问者方法
	VariableDeclaration(path) {
		// 该路径对应的节点
		const node = path.node
		// 实现对const和let 到 var 的替换
		['let', 'const'].includes(node.kind) && (node.kind = 'var')
	}


	// 箭头函数对应的访问者方法
    ArrowFunctionExpression(path){
    	let {id, params, body, generator, async} = path.node;

		path.replaceWith(types.functionExpression(id, params, body, generator, async))
    }
}

另一种是用于输入和输出双向处理,结构如下,参数 node 表示当前遍历的节点
let visitor = {
     ArrowFunctionExpression : {
        enter(node){
            
        },
        leave(node){
            
        }
    }
}
29. 事件循环
30. Promise封装

链接地址

function Promise((resolve, reject) => {
	if (true) {
		resolve('true');
	} else {
		reject('false');
	}
})

// 自定义Promise
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function Promise(executor) {
	const this_ = this;
	this_.value = null // 执行成功回调的传参
	this_.error = null // 执行失败回调的传参
	this_.onFulfilled = null // 成功的回调函数
	this_.onRejected = null // 失败的回调函数
	this_.status = PENDING
	this_.callbacks = [] // 成功或失败的回调数组,实现链式调用
	
	

	const resolve = (value) => {
		if (this_.status !== PENDING) return;

		this_.value = value;
		this_.status = FULFILLED;

		// 使得在异步状态下也可以得出正确的结果
		if (this_.callbacks.length !== 0) {
			this_.callbacks.forEach((item) => {
				item.onFulfilled(this_.value);
			})
		}
	}

	const reject = (error) => {
		if (this_.status !== PENDING) return;

		this_.error = error;
		this_.status = REJECTED;

		// 使得在异步状态下也可以得出正确的结果
		if (this_.callbacks.length !== 0) {
			this_.callbacks.forEach((item) => {
				item.onRejected(this_.error);
			})
		}
	}

	exector(resolve, reject);
}

Promise.prototype.then = function (onFulfilled, onRejected) {
	return new Promise((resolve, rejected) => {
		if (this.status === PENDING) {
			// 实现链式调用
			this.callbacks.push({
				onFulfilled: function(value) {
					// 如果传入的不是函数
					if (!isFunction(onFulfilled)) {
						resolve(value)
					} else {
						
					}
				},
				onRejected: onRejected,
			})
		} else if (this_.status === FULFILLED) {
			if (!isFunction(onFullfilled)) {
				resolve(this.value)
			} else {
				const result = onFullfilled(this.value);
				
				// 如果返回的是一个promise
				if (result instanceof Promise) {
					result.then((res) => {
              			resolve(res)
            		}, (rej) => {
              			reject(rej)
            		})
				} else {
					resolve(result)
				}				

			}
		} else if (this_.status = REJECTED) {
			if (!isFunction(onRejected)) {
				reject(this.error)
			} else {
				const result = onRejected(this.error);
				
				// 如果返回的是一个promise
				if (result instanceof Promise) {
					result.then((res) => {
              			resolve(res)
            		}, (rej) => {
              			reject(rej)
            		})
				} else {
					reject(result);
				}				

			}
		}
	})
}

Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}

无论当前 Promise 是成功还是失败,调用finally之后都会执行 finally 中传入的函数,并且将值原封不动的往下传。

Promise.prototype.finally = function(callback) {
	this.then((value) => {
		return Promise.resolve(callback).then(() => value)
	}, (error) => {
		return Promise.reject(callback).then(() => throw error)
	})
}


// Promise.all
// 必须等待所有的promise都执行完之后才能输出值
Promise.prototype.all = function(promises) {
	return new Promise((resolve, reject) => {
		let result = [];
		if (promises?.length === 0) {
			resolve(result);
			return;
		}

		const handleData = (data, index) => {
			result[index] = data;

			if (index === promises.length - 1) resolve(result);
		}

		for (let i = 0; i<promises.length; i++) {
			// 不直接使用promises[i].then是因为传入的promises数组项不能保证是promise
			Promise.resolve(promises[i]).then((res) => {
				handleData(res, i);
			}).catch((err) => {
				reject(err)
			})
		}
	})
}

// Promise.race
// 只要存在一个promise执行完即输出
Promise.prototype.race = function(promises) {
	return new Promise((resolve, reject) => {
		if (promises?.length === 0) {
			return;
		}

		for (let i = 0; i<promises.length; i++) {
			// 不直接使用promises[i].then是因为传入的promises数组项不能保证是promise
			Promise.resolve(promises[i]).then((res) => {
				resolve(res);
				return;
			}).catch((err) => {
				reject(err);
				return;
			})
		}
	})
}
31. 实现继承
32. JS常用的设计模式

设计模式

  1. 观察者模式(发布订阅模式):行为型模式
    “发布订阅模式相比观察者模式多了个事件通道,事件通道作为调度中心,管理事件的订阅和发布工作,彻底隔绝了订阅者和发布者的依赖关系。即订阅者在订阅事件的时候,只关注事件本身,而不关心谁会发布这个事件;发布者在发布事件的时候,只关注事件本身,而不关心谁订阅了这个事件。
    在这里插入图片描述
// addEventListener()也是一种:
target就是被观察对象Subject,listener就是观察者Observer。

target.addEventListener(type, listener [, options]);

观察者模式中Subject对象一般需要实现以下APIsubscribe(): 接收一个观察者observer对象,使其订阅自己
unsubscribe(): 接收一个观察者observer对象,使其取消订阅自己
fire(): 触发事件,通知到所有观察者

function Subject() {
	this.observes = [];
}

Subject.prototype = {
	const this_ = this;

	subscribe: function(observer) {
		this_.observers.push(observer)
	}

	unsubscribe: function(unObserver) {
		this_.observers = this_.observers.filter((item) => {
			item !== unObserver
		})
	}

	fire: function() {
		this_.observers.forEach(observer => {
      		observer.call(this_);
    	});
	}
}
  1. 代理模式:结构型模式
    es6里面的proxy
var obj = new Proxy({}, {
  get: function (target, propKey, receiver) {
    console.log(`getting ${propKey}!`);
    return Reflect.get(target, propKey, receiver);
  },
  set: function (target, propKey, value, receiver) {
    console.log(`setting ${propKey}!`);
    return Reflect.set(target, propKey, value, receiver);
  }
});

obj.count = 1
//  setting count!
++obj.count
//  getting count!
//  setting count!

// 创建一个Proxy的实例,第一个参数表示需要拦截的对象,也可以设定为{}空对象,第二个参数表示添加的拦截方法
var proxy = new Proxy(target, handler);
  1. 工厂模式:创建型模式
    简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。
function BenzCar(color) {
	this.color = 'black';
	this.brand = 'Benz';
}

function BMWCar(color) {
	this.color = 'black';
	this.brand = 'BMW';
}

function CarFactory() {
	this.create = (brand, color) => {
		switch(brand) {
			case 'Benz':
				return new BenzCar(color);
			case 'BMW':
				return new BMWCar(color);
			default:
				return 'null';
		}
	}
}

const carFactory = new CarFactory();
const cars = [];
 
cars.push(carFactory.create(BRANDS.suzuki, 'brown'));
cars.push(carFactory.create(BRANDS.honda, 'grey'));
cars.push(carFactory.create(BRANDS.bmw, 'red'));
 
function sayHello() {
  console.log(`Hello, I am a ${this.color} ${this.brand} car`);
}
 
for (const car of cars) {
  sayHello.call(car);
}
  1. 单例模式
    指的是创建的总是同一个实例。也就是使用类创建的实例始终是相同的。
function Instance(){}

function createInstance() {
	let instance = null
    
    return function() {
    	if (!instance) {
        	instance = new Instance();
        }
      
      	return instance;
    }
}

let create = createInstance();
let p1 = create(1);
let p2 = create(2);

console.log(p1 === p2)
33. 实现class和extends

https://juejin.cn/post/7001025002287923207

// class类
class Person {
    static instance = null;
    static getInstance() {
        return super.instance;
    }
	constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    sayHi() {
        console.log('hi');
    }
    sayHello = () => {
        console.log('hello');
    }
    sayBye = function() {
        console.log('bye');
    }
}


// 实现class 中不直接使用 = 来定义的方法,最终都会被挂载到原型上,使用 = 定义的属性和方法,最终都会被放到构造函数中。

var Person = (function () {
  function Person(name, age) {
    _classCallCheck(this, Person);

    this.sayHello = function () {
      console.log('hello');
    };
    
    this.sayBye = function () {
      console.log('bye');
    };
    
    this.name = name;
    this.age = age;
  }

  _createClass(Person, [{
    key: 'sayHi',
    value: function sayHi() {
      console.log('hi');
    }
  }]);

  return Person;
})();

Person.instance = null;

// extends
class Child extends Parent {
  constructor(name, age) {
    super(name, age);
    this.name = name;
    this.age = age;
  }
  getName() {
    return this.name;
  }
}

class Parent {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  getName() {
    return this.name;
  }
  getAge() {
    return this.age;
  }
}

// 实现

var Child = (function (_Parent) {
  _inherits(Child, _Parent);

  function Child(name, age) {
    _classCallCheck(this, Child);

    var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Child).call(this, name, age));

    _this.name = name;
    _this.age = age;
    return _this;
  }

  _createClass(Child, [{
    key: "getName",
    value: function getName() {
      return this.name;
    }
  }]);

  return Child;
})(Parent);


// _inherits()方法实现
function _inherits(subClass, superClass) { 
    // 如果有一个不是函数,则抛出报错
    if (typeof superClass !== "function" && superClass !== null) { 
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 
    } 
    // 将 subClass.prototype 设置为 superClass.prototype 的实例
    subClass.prototype = Object.create(superClass && superClass.prototype, { 
        constructor: { 
            value: subClass, 
            enumerable: false, 
            writable: true, 
            configurable: true 
        } 
    }); 
    // 将 subClass 设置为 superClass 的实例(优先使用 Object.setPrototypeOf)
    if (superClass) 
        Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 
}
34. 组合寄生式继承
function Person(name){
  this.name=name;
}
Person.prototype.sayName=function(){
  console.log(this.name+' '+this.gender+' '+this.age);
}
function Female(name,gender,age){
  Person.call(this,name);//第一次调用父类构造函数,考虑父类构造函数可能存在返回值     
  this.age=age;
  this.gender=gender;
}

 一般函数默认的prototype是一个类型为"object"的对象,它有两个属性:constructor和 __proto__。
 
Female.prototype=new Person();//第二次调用父类构造函数,将父类实例赋值到子类原型上
Female.prototype.constrcutor=Female;//因重写原型而失去constructor属性,所以要对constrcutor重新赋值

34. this指向问题
函数作用域中的this指向window

function Test() {
	this.name = 'xiaoming'; // 实际上是在window中定义了name属性
	console.log(this); // 指向的是window

	return function() {
		console.log(this.name); // 打印结果为'xiaoming',原因在于闭包情况下this指向全局,故可以访问到正确的name属性值
	}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Javascript基础知识整理—1 的相关文章

  • 对抗样本简介

    一 什么是对抗样本 对抗样本是一类被恶意设计来攻击机器学习模型的样本 它们与真实样本的区别几乎无法用肉眼分辨 xff0c 但是却会导致模型进行错误的判断 对抗样本的存在会使得深度学习在安全敏感性领域的应用收到威胁 如下图所示 xff0c 通
  • QT——QWidget窗体设置背景颜色、背景图、及其注意点

    QWidget窗体设置背景颜色 背景图 及其注意点 前言一 使用调色板QPalette来设置图片二 使用样式表QSS三 使用画笔Qpainter在paintEvent事件中绘制图片 前言 QT设置背景图笔录 一 使用调色板QPalette来
  • Navicat 连接Mysql 8.0以上版本报错1251的详细解决方案(配效果图)

    从MySql5 7版本跳到了Mysql 8 0的版本 xff0c 安装的时候提示链接加密方式已经改变 xff0c 是选择5 xff1f 时代的方式还是新的方式 xff0c 当时 全部选择默认 xff0c 所以选择的新的 加密方式 xff0c
  • 前端入门篇(一)常用五大浏览器及浏览器内核

    一 常见浏览器内核介绍 浏览器是网页运行的平台 xff0c 常见的浏览器有 IE 火狐 xff08 Firefox xff09 谷歌 xff08 Chrome xff09 Safari和Opera等 xff0c 我们平时称为五大浏览器 xf
  • 从输入URL到页面展示这中间发生了什么

    文章目录 从输入URL到页面展示这中间发生了什么Chrome多进程浏览器架构补充1 xff1a 进程和线程 整体的流程1 浏览器接收到输入的URL后 xff0c 先解析URL浏览器发送请求前 xff0c 尝试缓存命中 2 建立URL请求DN
  • 自动化标注技术

    自 动 化 标 注 技 术 自动化标注技术
  • 解决hadoop无法访问8088和19888端口的问题

    50070端口可以访问 xff0c 但是8088和19888端口不可以访问 xff0c 关防火墙 xff0c 看配置文件都是没有问题 xff0c 但就是不能web访问 看下图我的19888和8088前面都是127 0 0 1 xff0c 着
  • PyQt5有关QtWebEngineWidgets的解决办法

    from PyQt5 import QtWebKitWidgets ImportError DLL load failed 找不到指定的程序 这是因为PyQt5在5 6版本以后改用 QtWebEngineWidgets xff0c 所以之前
  • 在NER任务中CRF层的作用(例子)

    这里不过多阐述NER任务是啥了 xff0c 具体来看看他的作用 xff0c 并且举了一个例子 xff0c 让大家理解更加深刻 上图说明模型的输出是每个标签的分数 例如 xff0c 对于w0 模型的输出为1 5 B Person 0 9 I
  • 深度神经网络(DNN)通俗理解

    首先要明白俩个函数 xff0c 特别重要的俩个函数 线性的关系函数 激活函数 xff08 顾名思义是激活用的把得到的Z放入函数中 xff0c 来重新得到结果 xff09 中间的线相当于线性关系函数中的w 神经元相当于一个激活函数 xff0c
  • 爬虫写一个 “搜索引擎”,炒鸡简单。

    写搜索引擎前 xff0c 我们需要简答了解一下flask 这个轻量级的web的框架 xff0c 如果没有这个模块 只需要 pip install flask 即可 然后我们需要用html写一个最初的 搜索框 xff0c 并且命名这个文件为s
  • K-means聚类算法 伪代码 python3代码

    K means 算法及其代码 K means算法介绍K means 伪代码K means python 代码 K means算法介绍 链接 模式识别 聚类分析 K means 伪代码 计算两个点之间的欧式距离 span class toke
  • 模式识别—判别函数分类法(几何分类法)

    目录 统计模式识别之判别分析判别函数定义解释样例 判断函数正负值的确定确定判别函数的两个因素 线性判别函数一般形式性质两类情况多类情况 小结 广义线性判别函数目的 线性判别函数的几何性质模式空间与超平面概念讨论小结 权空间与权向量解概念线性
  • 【脚本】echo 输出赋值给变量

    链接 xff1a http zhidao baidu com link url 61 FMhso6Hf4eeRQN7p2qqzLOBAYPwh6yMJCWOvgmBFTDYWAEZ9ceuREtWhggxtcYG1iBhaJgqrcU7ad
  • 联邦学习 - 基础知识+白皮书+杨强教授讲座总结+同态加密+ 差分隐私

    联邦学习 兴起原因概念分类横向联邦学习纵向联邦学习联邦迁移学习 优势系统架构联邦学习与现有研究的区别联邦学习与差分隐私理论的区别联邦学习与分布式机器学习的区别联邦学习与联邦数据库的关系 联邦学习的最新发展及应用 xff08 2019第四届全
  • boomlab 实验 炸弹实验 系统级程序设计 CMU

    MENU boomlab还有30s到达实验1Step1 反汇编vim大法检查boom原因gdb调试出结果examinequit 实验二分析汇编语言ENDING 实验三答案 实验四func4 实验五实验六gdb调试 答案汇总ENDING问题解
  • CSAPP Lab:attacklab

    大小尾端 首先关于这个 xff0c 我一直没记清楚 xff0c 所以做个总结 xff1a 在裘宗燕翻译的 程序设计实践 里 xff0c 这对术语并没有翻译为 大端 和小端 xff0c 而是 高尾端 和 低尾端 xff0c 这就好理解了 xf
  • Advances and Open Problems in Federated Learning 总结翻译

    摘要 联邦学习 FL 是一种机器学习设置 xff0c 在这种设置中 xff0c 许多客户 例如移动设备或整个组织 在中央服务 器 例如服务提供商 的协调下协作地训练模型 xff0c 同时保持训练数据分散 FL体现了集中数据收集和最 小化的原
  • Multi-Center Federated Learning

    Multi Center Federated Learning Motivation 现有的联合学习方法通常采用单个全局模型来通过汇总其梯度来捕获所有用户的共享知识 xff0c 而不管其数据分布之间的差异如何 但是 xff0c 由于用户行为
  • No Fear of Heterogeneity: Classifier Calibration for Federated Learning with Non-IID Data

    No Fear of Heterogeneity Classifier Calibration for Federated Learning with Non IID Data Existing Methods for non IID da

随机推荐

  • Three scenarios for continual learning

    Three scenarios for continual learning Standard artificial neural networks suffer from the well known issue of catastrop
  • MQ2烟雾传感器

    1 MQ 2气体传感器所使用的气敏材料是在清洁空气中电导率较低的二氧化锡 SnO2 当传感器所处环境中存在可燃气体时 xff0c 传感器的电导率随空气中可燃气体浓度的增加而增大 使用简单的电路即可将电导率的变化转换为与该气体浓度相对应的输出
  • alembic

    alembic是sqlalchemy的作者开发的 用来做ORM模型与数据库的迁移与映射 alembic使用方式跟git有点了类似 xff0c 表现在两个方面 xff0c 第一个 xff0c alembic的所有命令都是以alembic开头
  • VScode远程免密登录

    安装配置python环境 xff1a 用VScode配置Python开发环境 xiaoj wang 博客园 cnblogs com VScode免密登录远程服务器 VS code ssh免密登陆 1 xff09 windows 下 xff0
  • linux虚拟机和主机能相互ping通,linux却不能访问外网

    linux虚拟机和主机能相互ping通 xff0c linux却不能访问外网 下面是试错过程 修改ifcfg eth0 xff08 名字可能不一样 xff09 vi etc sysconfig network scripts ifcfg e
  • 树莓派:树莓派的各个引脚

    由于第一次接触树莓派 xff0c xff0c xff0c emmmm xff0c 仔细写 xff0c 奥里给 3 3V 5V xff08 VCC xff09 xff1a 显然是电源正极啦 GND xff1a 接地用 xff0c 负极负极负极
  • 不分类工具:sd卡格式化工具安装教程

    下载地址 xff1a https www sdcard org downloads formatter 4 eula windows 进入上面这个链接 xff0c 你会看到满上面都是字 xff0c 有一个download xff0c 点完还
  • 不分类工具:Win32 DiskImager安装教程

    下载地址 xff1a http sourceforge net projects win32diskimager 这个也是很普普通通的下载安装 1 直接 download 2 双击安装文件 xff0c 弹出如下框 xff0c 选择我同意 x
  • Meta-Learning: Learning to Learn Fast

    Meta Learning Learning to Learn Fast 元学习 学习如何学习 译 原文 本文与原文基本没有区别 xff0c 仅供个人学习记录 电子笔记本 前言 xff1a 元学习解决 xff1a 遇到没有见过的任务 xff
  • 解决 Docker 容器时间与本地时间不一致的问题

    Linux 通过 Date 命令查看系统时间 xff0c 得到如下结果 xff1a root 64 iZ8vbg6m7f5ntzibw3t4huZ date Mon Aug 26 12 24 58 CST 2019 但是在 Docker 容
  • 记录ssh 和vnc命令

    ssh windows是客户端 linux是服务端 在windows powershell 输入 ssh rikirobot 64 192 168 x xxx xff08 ip地址 xff09 VNC Viewer 参考文章 xff1a 1
  • Redux源码解析(部分)

    相信用过React的小伙伴对于Redux一定不陌生 xff0c A Predictable State Container for JS Apps xff0c 这是官方文档对于Redux的定义 xff0c 即一款适用于JS Apps的可预测
  • Axios源码解析(部分)

    从 Github 上把 Axios 项目的 master 分支拷贝到本地 xff0c 用编辑器打开项目目录 首先我们先解析一下整个 Axios 项目的一些关键的文件结构 对照项目的文件目录 xff0c 梳理一下其中的一些关键文件夹以及它的作
  • 解析Javacript的事件循环机制

    前言 作为最受欢迎的Web编程语言 xff0c Javascript的单线程执行是其一大特点 xff0c 也就是说在同一时间只能有一个任务处于执行状态 xff0c 而后续的任务需要等待当前任务处理完毕后才能继续处理 xff0c 而在当前编程
  • 关于项目打包相关的优化问题

    本文主要是针对项目在打包过程中使用的一些Webpack相关的打包手段 1 首先在真正处理我们的打包之前 xff0c 我们可以通过安装插件来具体查看一下我们目前项目中的打包模块以及打包时间 打包后各个模块的可视化工具 webpack bund
  • 英语词汇、短语、语句积累

    rules of thumb 经验法则
  • 关于Webpack plugins插件的两种写法

    类写法以及require方式引入 span class token keyword class span span class token class name HelloWorld span span class token punctu
  • 关于mini-css-extract-plugin在使用过程中出现冲突的问题

    今天在优化打包的过程中 xff0c 运行npm run build后 xff0c 总是会出现冲突的警告信息 xff0c 而且对于我的这个项目而言出现了几十条的冲突提示 如下 问题排查 首先我以为可能是由于我新引入的优化方面的插件导致了原先的
  • 关于使用Antd中的DatePicker出现的日期格式转化问题(Dayjs和Momentjs)

    在测试过程中发现了一个比较有意思的bug问题 xff0c 我们使用的是antd中的DatePicker组件 xff0c 当时间选择框存在已经设定的初始值后 xff0c 点击时间选择框直接报错 xff0c 但是当清除内容或者处于新建没有默认值
  • Javascript基础知识整理—1

    1 JS数据类型 原始数据类型 xff1a boolean xff0c string xff0c null xff0c undefined xff0c symbol xff0c bigint xff0c number 引用类型 xff1a