【记录】前端知识点 - JS

内置类型

(1)基本数据类型

number

  • 浮点型,基于IEEE 754标准的长浮点数(共64位:1位数符,11位阶码_移码,52位尾数_原码)实现。

string

boolean

undefined

null

symbol

(2)引用数据类型

Object

function

array

null和undefined的异同点

相同点

  • 转为布尔值都是false。

undefined

  • 定义了但未赋值。
  • typeof(undefined)=‘undefined’
  • Number(undefined)=NaN

null

  • 定义了并且赋值了,值为null。
  • typeof(null)=‘Object’
  • Number(null)=0

判断数据类型

(1)typeof(obj)

以字符串的形式返回一个值的数据类型。

  • 可以判断number/string/boolean/undefined/symbol/function。
  • 不能判断null(返回object)、array(返回object)。
  • NaN的类型是number。
  • null的类型是object。因为1995年的 JavaScript 语言第一版,只设计了五种数据类型(对象、整数、浮点数、字符串和布尔值),没考虑null,只把它当作object的一种特殊值。后来null独立出来,作为一种单独的数据类型,为了兼容以前的代码,typeof null返回object就没法改变了。

(2)A instanceof B

检查一个对象是否是一个类的实例(检测构造函数B的 prototype 属性是否出现在某个实例对象A的原型链上)。

  • 所有对象都是Object的后代。
  • 可以判断array。
function instanceOf(a, b){
	//获取构造函数的显式原型
	let bp = b.prototype;
	//获取实例的隐式原型
	let ap = a.__proto__;
	//判断是否在ap的原型链上
	while(ap){
		if(ap === bp){
			return true;
		}
		ap = ap.__proto__;
	}
	return false;
}

(3)Object.prototype.toString.call(obj)

判断一个变量是不是undefined

//undefined不是保留字,在低版本浏览器能被赋值。用以下方法判断会出错。
a === undefined
//void后面随便加上一个表达式,返回是undefined。
a === void 0

类型转换

(1)转Boolean

//数值=>布尔值
//0(-0)和NaN为false,其他都为true
Boolean(0) // false
Boolean(-0) // false
Boolean(NaN) // false

//字符串=>布尔值
//空串为false,其他都为true
Boolean('') // false

//null=>布尔值
Boolean(null) // false

//undefined=>布尔值
Boolean(undefined) // false

(2)转Number

//布尔值=>数值

//字符串=>数值
//空串、全是空格为0
Number('') // 0
Number('   ') // 0
//字符串含有非数值的内容为NaN
Number('a') // NaN

//null=>数值
Number(null) // 0

//undefined=>数值
Number(undefined) // NaN

(3)转String

(4)对象转基本类型

对象转基本类型时,先尝试调用Symbol.toPrimitive,再调用valueOf()和toString()。

Symbol.toPrimitive

  • 返回该对象对应的原始类型值。

valueOf()

  • 默认返回对象自身。

toString()

  • 默认返回字符串。
let obj = {
	//hint表示当前运算的模式
	[Symbol.toPrimitive](hint){
		switch(hint){
			//需要转成数值
			case 'number':
				return 123;
			//需要转成字符串
			case 'string':
				return 'str';
			//可以转成数值,也可以转成字符串
			case 'default':
				return 'default';
			default:
				throw new Error();
		}
	}
};



let a = {
  valueOf() {
    return 0;
  },
  toString() {
    return '1';
  },
  [Symbol.toPrimitive]() {
    return 2;
  }
}
1 + a // => 3
'1' + a // => '12'

(5)运算符

隐式转换规则

  • 转为string
    +(字符串连接符:一方是字符串or都不是字符串或数值时)
  • 转为number
    ++、–(自增自减运算符)
    ±*/%(算术运算符)
    ==、!=、===、!===(关系运算符)
  • 转为boolean
    !(逻辑非运算符)
1 + '1' // '11'

[1, 2] + [2, 1] // '1,22,1'
//[1, 2].toString() => '1,2'
//[2, 1].toString() => '2,1'

'a' + + 'b'
//先一元运算,+'b' => NaN
//'a' + NaN => 'a' + 'NaN' => 'aNaN'

2 * '2' // 4

+

  • 一元运算符。
  • 加号 + 应用于单个值,对数字没有任何作用。但是如果运算元不是数字,加号 + 则会将其转化为数字。
[] == ![] // true
//![] => 布尔值false => 数值0
//[] => ''(对象转基本类型调用[Symbol.toPrimitive],然后toString())
//   => 数值0

(6)运算符运算顺序

一元运算符>二元运算符。

a==1&&a==2&a==3

//对象类型转换
//当使用==进行比较时,会先类型转换,再进行比较。当Object和Number类型进行比较时,Object类型会转换为Number类型;转换时,会尝试调用Object.valueOf()和Object.toString()来获取对应的数字基本类型。
var a = {
	i: 1,
	toString: function(){
		return a.i++;
	}
};
console.log(a==1&&a==2&&a==3)//true

//数组类型转换
//当使用==进行比较时,会先类型转换,再进行比较。当Array和Number类型进行比较时,Array类型会转换为Number类型;转换时,会先调用Array.toString(),Array.toString()会隐含调用Array.join()方法。此时我们让Array.join()等于Array.shift()方法即可。
var a = [1, 2, 3];
a.join = a.shift;

//defineProperty
var val = 0;
Object.defineProperty(window, 'a', {
	get: function(){
		return val++;
	}
});

var、let、const的区别

var

  • 存在变量、函数提升。
  • 全局作用域。
  • 可以重复声明。

let

  • 不存在变量、函数提升。
  • 块级作用域。
  • 不可以重复声明。

const

  • 不存在变量、函数提升。
  • 块级作用域。
  • 不可以重复声明。
  • 定义时需赋值。
  • 赋值后一般不可修改。如果赋的是基本数据类型,不可修改。如果赋的是引用数据类型,可以修改。

函数的length

//没有指定默认值,length=参数个数。
//指定了默认值,length=没有指定默认值的参数个数。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
//不计入rest参数。
(function (...args){}).length // 0
//不计入设置了默认值的参数后面的参数个数。
(function (a = 0, b, c){}).length // 0
(function f(name, age=18, gender, ...args){}).length // 1

//面试题
123['toString'].length + 123//124
//123相当于new Number()
//顺着隐式原型链,new Number()=>Number.prototype,找到toString属性(方法是特殊的属性),toString.length=1

对象创建模式

//new Object()创建
//语句太多了
var obj = new Object();
obj.name = 'tom';
obj.age = 12;
obj.setName = function(name){
	this.name = name;
}

//字面量创建
//如果要创建多个对象,存在重复代码
var obj = {
	name: 'Tome',
	age: 12,
	setName: function(name){
		this.name = name;
	}
};

//工厂模式创建
//无法区分类型
function createPerson(name, age){
	var obj = {
		name: name,
		age: age,
		setName: function(name){
			this.name = name;
		}
	};
	return obj;
}
var p = createPerson('tom', 12);

//构造函数创建
//每个数据都有相同数据(函数),浪费内存
function Person(name, age){
	this.name = name;
	this.age = age;
	this.setName = function(name){
			this.name = name;
	};
}
var person = new Person('tom', 12);

//构造函数+原型
function Person(name, age){
	this.name = name;
	this.age = age;
}
Person.prototype.setName = function(name){
	this.name = name;
};

手写new

function myNew(fn, ...args){
	//创建空对象
	let obj = {};
	//实例的隐式原型指向构造函数的显式原型
	obj.__proto__ = fn.prototype;
	//绑定this,执行构造函数
	fn.apply(obj, args);
	return obj;
}


let o1 = new fn();
//等价于
let o2 = myNew(fn);

this

根据函数的调用方式不同,this会指向不同的对象。

//以函数的形式调用:this指向全局对象window。(在strict模式下,不会指向全局对象,而是undefined)
fun();
//以方法的形式调用:this指向调用方法的对象。
obj.fun();
//以构造函数的形式调用时,this指向新创建的实例对象。
function Person(name){
	this.name = name;
	console.log(this);
}
let person = new Person('abc');
//使用call、apply、bind时,this指向指定的对象。

闭包

定义

  • 引用了另一个函数作用域中变量的函数。

产生的条件

  • 函数嵌套。
  • 内部函数引用了外部函数的数据(变量/函数)。

作用

  • 读取外部函数内部的变量。
  • 让这些变量始终保持在内存中。
    外层函数执行后,其执行上下文会被清除。但是由于闭包用到了外层函数的活动对象,导致外层函数的活动对象不能从内存释放。只要闭包没有被垃圾回收机制清除,就一直保持着对外层函数的活动对象的引用,外层函数的活动对象就一直保存在内存中。(至于是全部保存还是只保存被引用的,取决于JS引擎使用的GC机制。https://www.iteye.com/blog/justjavac-1465169)

应用

  • 实现JS模块。
  • 防抖节流。
  • 回调函数(事件监听器、ajax回调、定时器……

优点

  • 希望一个变量长期存储在内存中。
  • 避免全局变量污染。
  • 私有成员的存在。

缺点

  • 常驻内存,增加内存使用量。
  • 使用不当会造成内存泄露。

http://cavszhouyou.top/JavaScript%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E4%B9%8B%E9%97%AD%E5%8C%85.html

//实现每隔1s打印1, 2, 3, 4,5
//闭包:让i的值常驻内存,当输出j时,引用的是外部函数循环时的i值。
for(var i = 1; i <= 5; i++){
	(function(j){
		setTimeout(()=>{
			console.log(j);
		}, j * 1000);
	})(i);
}
//let:利用块级作用域
for(let i = 1; i <= 5; i++){
	setTimeout(()=>{
		console.log(i);
	}, i * 1000);
}
//setTimeout第三个参数:一旦定时器到期,它们会作为参数传递给回调函数。
//每次循环中将i值变成setTimeout的第三个参数传入。
for(var i = 1; i <= 5; i++){
	setTimeout((j)=>{
		console.log(j);
	}, i * 1000, i);
}

深浅拷贝

浅拷贝

  • 只拷贝第一层,深层只是引用。(类似软链接)
  • 新对象的更改会影响原对象。
  • 新对象与原对象共享相同的对象。

深拷贝

  • 层层拷贝。(类似全部复制)
  • 新对象的更改不会影响原对象。
  • 新对象与原对象不共享相同的属性。
//浅拷贝
let a = {
	age: 1
};
//Object.assign(target, origin):将源对象的所有可枚举属性(包括symbol)复制到目标对象,返回目标对象。
let b = Object.assign({}, a);
//展开运算符...
let b = {...a};


//深拷贝
//json
//如果有字段值为undefined、function、symbol,字段直接消失;如果有字段值为NaN、+-Infinity,字段变成null;如果有字段为RegExp对象,字段变成{};如果有环引用,直接报错。
let b = JSON.parse(JSON.stringfy(a));
//用lodash的cloneDeep()
//不能复制函数。
//let b = _.cloneDeep(a);
//手写深拷贝(待填坑)

模块化规范CommonJS和ES6的区别

  • CommonJS支持动态导入(require(${path}/xx));ES6不支持,但已有提案。
  • CommonJS是同步导入,因为用在服务端,文件都在本地,即使卡住主线程,影响也不大;ES6是异步导入,因为用在浏览器,需要下载文件,如果采用同步导入对渲染会有很大影响。
  • CommonJS在导出时是深拷贝,修改导出的值,导入的值也不会改变,需要重新导入来更新;ES6在导出时是浅拷贝,修改导出的值,导入的值会发生变化。

防抖、节流

防抖

  • 频繁去触发一个事件,但是只触发最后一次,以最后一次为准。
  • 应用场景(大量事件一次响应)
    (1)防止频繁点击按钮提交表单。
    (2)防止input框变化频繁触发事件。
//在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。
//不带immediate选项(搜索)
function debounce(func, delay = 500){
	let timer;
	return function(...args){
		//如果已经有定时器,清空定时器
		if(timer) clearTimeout(timer);
		timer = setTimeout(()=>{
			//this为调用debounce的对象
			func.apply(this, args);
		}, delay);
	}
}
button.addEventListener('click', debounce(payMoney, 1000));

节流

  • 频繁去触发一个事件,但是只能每隔一段时间触发一次。
  • 应用场景(大量事件按时间平均分配触发)
    (1)在滚动加载或者监听滚动条位置的场景下,用户在滚动页面时,每隔一段时间执行一次函数,而不是每次滚动就执行函数。
    (2)搜索框的联想搜索。
    (3)注册时手机验证码。
//每次触发判断是否在时间间隔内,如果在则不触发事件,如果不在则设置定时器并触发事件。
//定时器
function throttle(func, delay){
	let timer;
	return function(){
		//如果已经有定时器,返回。
		if(timer) return;
		timer = setTimeout(()=>{
			func.apply(this, arguments);
			//清空定时器
			timer = null;
		}, delay);
	}
}
//Date()
function throttle(func, delay){
	let pre = 0;
	return function(...args){
		let now = new Date();
		if(now - pre > delay){
			func.apply(this, args);
			pre = now;
		}
	}
}	
window.addEventListener('resize', throttle(coloring, 2000));

继承

function Animal(name){
	this.name = name || 'Animal';
	this.sleep = function(){
		console.log(this.name+'is sleeping...');
	}
}
Animal.prototype.eat = function(food){
	console.log(this.name + 'is eating the' + food);
}



//原型链继承:将父类的实例作为子类的原型
/*
	优点:1、实例既是子类实例,又是父类实例。
		 2、父类新增原型属性,子类都能访问到。
		 3、简单,易于实现。
		 
	缺点:1、想为子类添加属性和方法,必须在new之后执行,不能放在构造器中。
		 2、子类实例既继承父类实例属性,也继承父类原型属性。
		 3、创建子实例时,无法向父类构造函数传参。
		 4、不支持多继承(继承多个父类)。
*/
function Cat(){}
Cat.prototype = new Animal();
//Cat.prototype = Object.creat(Animal.prototype)
Cat.prototype.name = 'cat';

var cat = new Cat();
console.log(cat.name); // 'cat'
cat.eat('fish'); // 'cat is eating the fish'
cat.sleep(); // 'cat is sleeping...'
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true




//构造继承
/*
	优点:1、解决了原型链继承中,子类实例继承父类原型属性的问题。
		 2、创建子类实例时,可以向父类传递参数。
		 3、支持多继承。

	缺点:1、子类实例不是父类实例。
		 2、子类实例只继承父类实例属性。
*/
function Cat(name){
	Animal.call(this);
	this.name = name || 'Tom';
}

var cat = new Cat();
console.log(cat.name); // 'Tom'
cat.sleep(); // 'Tom is sleeping...'
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true




//实例继承
/*
	优点:不限制调用方式。
	
	缺点:1、实例是父类的实例,不是子类的实例。
		 2、不支持多继承。
*/
function Cat(name){
	var instance = new Animal();
	instance.name = name || 'Tom';
	return instance;
}

var cat = new Cat();
console.log(cat.name);
cat.sleep;
console.log(cat instanceof Animal);// true
console.log(cat instanceof Cat);// false




//拷贝继承
/*
	优点:支持多继承。
	
	缺点:1、效率低,内存占用高。
		 2、无法获取父类的不可枚举方法(for...in访问不到)。
*/
function Cat(name){
	var animal = new Animal();
	for(var p in animal){
		 Cat.prototype[p] = animal[p];
	}
	this.name = name || 'Tom';
}

var cat = new Cat();
console.log(cat.name);
cat.sleep();
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true




//组合继承(构造继承+原型链继承)
/*
	优点:1、既继承父类的实例属性,又继承父类的原型属性。
		 2、实例既是子类的实例,又是父类的实例。
		 3、不存在
		 4、构造函数可以传参。
	
	缺点:调用两次父类构造函数,生成了两份实例。
*/
function Cat(name){
	Animal.call(this);
	this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

var cat = new Cat();
console.log(cat.name);
cat.sleep();
cat.eat('fish');
console.log(cat instanceof Animal);// true
console.log(cat instanceof Cat);// true




//寄生组合继承:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造时,就不会初始化两次实例方法/属性,避免继承组合的缺点。
function Cat(name){
	Animal.call(this);
	this.name = name || 'Tom';
}
var Super = function(){};
Super.prototype = Animal.prototype;
Cat.prototype = new Super();

var cat = new Cat();
console.log(cat.name);
cat.sleep();
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

call、apply、bind

/*
  联系:都用于修改this指向。第一个参数都是this的指向对象。
  区别:call的其他参数直接写入,用逗号分隔开。
	   apply的其他参数要放在一个数组中传入。
	   bind的其他参数直接写入,用逗号分隔开。但是它返回的是一个函数(相当于修改过this指向的函数)。调用时要在后面加一个括号,表示立即执行。
*/
obj.myFun.call(db,'成都','上海');
obj.myFun.apply(db, ['成都','上海']); 
obj.myFun.bind(db,'成都','上海')('杭州');


//手写call
Function.prototype.myCall = function (obj, ...args){
	//不传入第一个参数,则默认为window。
	obj = obj || window;
	//防止键名重复
	const fn = Symbol();
	//this指的是fun;在obj身上添加fun方法。
	obj[fn] = this;
	const res = obj[fn](...args);
	delete obj[fn];
	return res;
}
fun.myCall(obj, 'a', 'b');


//手写apply
Function.prototype.myApply = function(obj, args){
	obj = obj || window;
	const fn = Symbol();
	obj[fn] = this;
	const res = obj[fn](...args);
	delete obj[fn];
	return res;
}


//手写bind(柯里化)
Function.prototype.myBind = function(obj, ...args){
	obj = obj || window;
	const _this = this;
	const res = function(...innerArgs){
		//如果使用了new,this会绑定到新创建的实例对象
		//判断是否使用了new
		if(this instanceof res){
			_this.apply(this, [...args, ...innerArgs]);
		}else{
			_this.apply(obj, [...args, ...innerArgs]);
		}
	};
	res.prototype = _this.prototype;
	return res;
}
fun.myBind(obj, 'a', 'b')('c');
//new
var ff = fun.myBind(obj, 'a', 'b');
var f = new ff('c');

Promise

async/await

//async相当于用Promise.resolve包裹处理
async function fn(){
	//await的下一句相当于用Promise.then处理
	await request(1);
	await request(2);
	//没有return语句,则返回的Promise对象的值为undefined
	//return 3;
}
console.log(fn()); // Promise {<fulfilled>: undefined}

async function fn(){
	const res1 = await request(5);
	const res2 = await request(res1);
	console.log(res2);
}

Generator

//第一个使用next(),传递参数是无效的。
function* gen(){
	const num1 = yield 1;
	const num2 = yield 2;
	return 3;
}
const g = gen();
console.log(g.next()); // {value: 1, done: false}


//用generator函数实现async/await(填坑吧)

async/await和generator的区别

async/await返回值是promise对象,generator不是。
generator需要自己执行相应的操作来实现排队效果。

高阶函数

参数是函数,返回值也是函数。

柯里化

curring

  • 将多参数的函数转换成单参数的形式。
//函数参数复用
function uri_currying(protocol){
	return function(hostname, pathname){
		return `${protocol}${hostname}${pathname}`;
	};
}
const uri_https = uri_currying('https://');
console.log(uri_https('www.baidu.com', '/a'));
//'https://www.baidu.com/a'

//判断兼容性
const whichEvent = (function(){
	if(window.addEventListener){
		return function(element, type, listener, useCapture){
			element.addEventListener(type, function(e){
				listener.call(element, e);
			}, useCapture);
		}
	}else if(window.attachEvent){
		return function(element, type, handler){
			element.attachEvent('on'+type, function(e){
				handler.call(element, e);
			});
		}
	}
});//使用立即执行函数则whichEvent直接等于两个之中的一个函数。

//延迟执行 bind

函数重载

function fn(name) {
  console.log(`我是${name}`);
}
function fn(name, age) {
  console.log(`我是${name},今年${age}`);
}
function fn(name, age, sport) {
  console.log(`我是${name},今年${age}岁,喜欢运动是${sport}`);
}
//理想结果
fn('林三心'); // 我是林三心
fn('林三心', 18); // 我是林三心,今年18岁
fn('林三心', 18, '打篮球'); // 我是林三心,今年18岁,喜欢运动是打篮球



//arguments
function fn(){
	switch(arguments.length){
		case 1:
			var [name] = arguments;
			consoel.log(`我是${name}`);
			break;
		case 2:
			var [name, age] = arguments;
  			console.log(`我是${name},今年${age}`);
			break;
		case 3:
			var [name, age, sport] = arguments;
  			console.log(`我是${name},今年${age}岁,喜欢运动是${sport}`);
			break;
	}
}

//利用闭包保存引用
function addMethod(object, name, fn){
	//保存前一次的方法
	var old = object[name];
	//重写方法(执行的时候才调用方法)
	object[name] = function(){
		//如果调用时,传入的参数和已传入方法的参数长度一样,则直接调用。
		if(fn.length === arguments.length){
			return fn.apply(this, arguments);
		//否则判断old是否为函数,则调用
		}else if(typeof old === 'function'){
			return old.apply(this, arguments);
		}
	};
}
addMethod(window, 'fn', (name) => console.log(`我是${name}`));
addMethod(window, 'fn', (name, age) => console.log(`我是${name},今年${age}`));
addMethod(window, 'fn', (name, age, sport) => console.log(`我是${name},今年${age}岁,喜欢运动是${sport}`));

V8垃圾回收机制

(1)

基本数据类型

  • 拥有固定的大小;栈内存保存值;直接访问。

引用数据类型

  • 大小不固定;栈内存保存指针,指针指向堆内存中的对象空间;通过引用访问。

由于栈内存所存的基础数据类型大小是固定的,所以栈内存的内存都是操作系统自动分配、释放回收的。由于堆内存所存的大小不固定,系统无法自动释放回收,需要JS引擎来手动释放内存。

IMG_1845.jpg
(2)回收算法

Scavenage算法

  • 根据可达性,标记活动对象和非活动对象。
  • 把from-space的活动对象复制到to-space并排序。
  • 清除from-space的非活动对象。
  • from-space和to-space互换,以便下次进行垃圾回收。

Mark-Sweep算法

  • 标记活动对象和非活动对象。
  • 清除非活动对象。

Mark-Compact算法

  • 标记活动对象和非活动对象。
  • 清除非活动对象。
  • 将活动对象紧凑到一起。

(3)

全停顿Stop-The-World

  • 垃圾回收优先于代码执行,两者同时进行时,会先停止代码的执行,等到垃圾回收完毕再执行代码。

Orinoco优化

  • Orinoco是V8垃圾回收器的项目代号。为了解决全停顿问题,提出了增量标记、懒性清理、并发、并行的优化方法。

增量标记Incremental Marking

  • 标记一段,JS代码运行一段时间。

懒性清理Lazy Sweeping

  • 当剩余空间足以让JS代码跑起来时,延迟清理或者只清理部分垃圾。

并发

  • 允许在垃圾回收的同时不需要将主线程挂起,两者可以同时进行,只需要在个别时候停下来对垃圾回收器做一些特殊的操作。

并行

  • 允许主线程和辅助线程同时执行同样的GC工作,这样可以让辅助线程来分担主线程的GC工作,使得垃圾回收所耗费的时间等于总时间除以参与的线程数量(加上一些同步开销)。

写屏障

  • 解决对象引用改变、标记错误等现象。

装箱拆箱

装箱

  • 将基本数据类型转换为对应的引用数据类型。
const s = 'a_a';
const index = s.indexOf('_');
console.log(inde); // 1

//等同于(进行了装箱操作)
//1、创建对应类型的实例
var temp = new String('a_a');
//2、在实例上调用指定的方法
const index = temp.indexOf('_');
//3、销毁实例
temp = null;
console.log(index); // 1

拆箱

  • 将引用数据类型转换为对应的基本数据类型的操作。
//通过valueOf实现拆箱
var objNum = new Number(123);
var objStr = new String('123');
console.log(typeof objNum);//'object'
console.log(typeof objStr);//'object'

console.log(tyeof objNum.valueOf());//number
console.log(tyeof objStr.valueOf());//number

如何避免内存泄漏

减少全局变量。

及时清理定时器。

合理使用闭包。

及时解除对分离DOM的引用。

  • 分离DOM:分离的DOM元素是已从DOM中删除的元素,但是由于JavaScript的原因,它们的内存仍然保留。这意味着只要元素对任何变量或对象的引用在任何地方,即使从DOM中销毁后也不会进行垃圾回收。
<button id="btn">点击</button>

//虽然已从DOM中删除,但仍保存着引用,则仍存在内存之中。
let btn = document.getElementById('btn');
document.body.removeChild(btn);

//解除引用。
btn = null;

模块化和模块化规范

模块化是为了解决命名冲突、污染问题,良好的模块化设计可以降低代码之间的耦合性,提高代码的可维护性、可复用性。
模块化规范是为了统一JS模块的定义和加载机制,提高开发效率。

CommonJS

  • 主要是Node.js使用。(同步加载不适用于浏览器端)
  • 通过require同步加载模块,exports导出。(首次加载会缓存)
  • 难以支持模块静态分析(在代码运行前就可以判断哪些代码使用到了,哪些代码没使用到)。

AMD

  • 主要是浏览器端使用。
  • 通过require异步加载模块,define定义模块和依赖。依赖前置。
  • 难以支持模块静态分析。

CMD

  • 主要是浏览器端使用。
  • 通过require异步加载模块,define定义模块和依赖。依赖就近。
  • 难以支持模块静态分析。

ES6

  • 现代浏览器原生支持。
  • 通过import异步加载模块,exports导出。
  • 支持模块静态分析。

Symbol()、Symbol.for()和Symbol.keyFor()

Symbol()

  • 直接返回一个新的symbol值。

Symbol.for()

  • 先根据key,在全局symbol注册表中查找对应的symbol,如果查找到就返回;否则就创建一个与该key关联的symbol,并放入全局symbol注册表中。
let yellow = Symbol('Yellow');
let yellow1 = Symbol.for('Yellow');
yellow === yellow1; // false

let yellow2 = Symbol.for('Yellow');
yellow1 === yellow2; // true

Symbol.keyFor()

  • 获取全局symbol表中与某symbol值相关联的键值。
let yellow1 = Symbol.for('Yellow');
Symbol.keyFor(yellow1); // 'Yellow'

for…in

遍历key。

for…of

  • 内部通过Symbol.iterator实现。
  • object不能用for…of遍历。
for(let key of Object.keys(obj)){
	console.log(obj[key]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值