文件上传

前端,可以使用 HTML的<input type="file"> 元素来实现文件上传。当用户选择了一个或多个文件后,可使用 Javascript来获取这些文件并将它们上传到服务器。

// 伪代码样例
<input type="file" id="fileInput" multiple>
<button id="uploadButton">上传</button>
<script>
	const fileInput = document.querySelector('#fileInput');
	const uploadButton = document.querySelector('#uploadButton');
	uploadButton.addEventListener('click',()=>{
		const files =fileInput.files;
		// 进行文件的是否上传,大小,格式等判断和限制
		if(files.length===0){
			alert('请选择文件');
			return;
		}
		// 获取上传文件数据转为formdata格式传输
		const formData = new FormData();
		for(const file of files){
			formData.append('files[]',file);
		}	
		// 调用上传接口(伪代码)
		fetch('/upload',{
			method:'POST'
			body: formData
		}).then(response => response.text())
		.then(result => {
			console.log(result);
		})
		.catch(error =>{
			console.error(error);
		});
	});
</script>

分片上传

分片上传是一种文件上传技术,它允许将大文件分割成多个较小的片段(通常称为“分片"或"块”),然后将这些片段分别上传到服务器。服务器在接收到所有分片后,会将它们组合成一个完整的文件。
优点:

  1. 大文件上传。由于文件被分割成多个较小的片段,因此可以避免单次上传文件过大导致的问题。
  2. 提高上传速度。由于每个分片都是独立上传的,因此可以并行上传多个分片,从而提高上传速度。
  3. 支持断点续传。如果某个分片上传失败,可以重新上传这个分片,而不需要重新上传整个文件。
  4. 实现分片上传需要前端和后端配合。前端需要将文件分割成多个分片,并将每个分片单独上传到服务器;后端需要接收这些分片,并在接收到所有分片后将它们组合成一个完整的文件。
// 伪代码样例
<input type="file" id="fileInput" multiple>
<button id="uploadButton">上传</button>
<script>
	const fileInput= document.querySelector('#fileInput');
	const uploadButton=document.querySelector('#uploadButton');
	uploadButton.addEventListener('click',async()=>{
		const file =fileInput.files[0];
		if(!file){
			alert('请选择文件');
			return;
		}
		// 前端分块处理
		const chunkSize=1024*1024;//每个分片的大小(1MB)
		const totalChunks=Math.ceil(file.size /chunkSize);// 分片总数
		for(let i=0;i<totalChunks; i++){
			const start=i*chunkSize;
			const end=Math.min(file.size,start + chunkSize);
			const chunk=file.slice(start, end); //文件分割成多个部分
			
			// 根据后端的请求格式和数据组装传入接口...
			const formData =new FormData();
			formData.append('file', chunk);
			formData.append('filename', file.name);
			formData.append('chunkIndex',i); //当前是第几片
			formData.append('totalChunks', totalChunks);
			await fetch('/upload',{
				method:'POST'
				body: formData
			});
			// 可根据需求添加Progress等进度提示...
		}
		alert('上传完成')
	});
	// 大文件上传根据需求是否需要前端mergeFiles,大致同理调用和请求接口
</script>

File对象和blob对象

File 对象和 Blob 对象都表示一个二进制数据块,它们都继承自 Blob 类。它们之间的主要区别在于 File 对象除了包含二进制数据外,还包含了文件的元数据(如文件名和修改日期)。

  1. 使用 File 对象来表示用户选择的文件,例如通过 元素或拖放操作选择的文件。也可以使用 File构造函数来创建一个新的 File对象,但是这通常不太常用。
  2. Blob 对象通常用于表示一个不与文件系统相关联的二进制数据块。可以使用 Blob 构造函数来创建一个新的 Blob 对象,或者使用 File对象的 slice 方法来从文件中获取一个数据块。
  3. Blob对象是包含File的;
  //例如导出json数据
  download() {
    var eleLink = document.createElement('a');
    eleLink.download = this.props.modelSummary.mname + '.json';
    eleLink.style.display = 'none';
    var blob = new Blob([JSON.stringify(this.props.newModelFlowInfo)]);
    eleLink.href = URL.createObjectURL(blob);
    document.body.appendChild(eleLink);
    eleLink.click();
    document.body.removeChild(eleLink);
  }

definePropetry的作用

0bject.defineProperty 方法用于在对象上定义一个新属性,或者修改对象上已有的属性。它允许你精确地控制属性的行为,例如属性是否可枚举、可写、可配置等。

  const obj ={};
  	Object.defineProperty(obj,'myProperty',{
  		value: 42,
  		writable: true,
  		enumerable: true,
  		configurable: true
 	})
 	console.log(obj.myProperty);//输出:42

es6 proxy的作用是什么

Proxy 是 ES6 中新增的一个构造函数,它用于创建一个代理对象,可以拦截并自定义对象的基本操作,例如属性查找、赋值、枚举、函数调用等。

//定义了一个目标对象 target 和一个处理器对象 handler
const target={
	message1:'hello',
	message2:'world'
};
const handler ={
	get:function(target,prop,receiver){
		if(prop==='message2'){
			return 'everyone';
		}
		return Reflect.get(...arguments);
	}
};
// 使用 new Proxy(target,handler)来创建一个代理对象。当我们访问代理对象的属性时,会调用处理器对象中的 get 方法。在这个方法中,我们可以自定义属性访问的行为
const proxy = new Proxy(target, handler);
console.1og(proxy.message1);// 输出: hello
console.log(proxy.message2);//输出:everyone

es6 reflect的作用是什么?

Reflect 是 ES6 中新增的一个内置对象,它提供了一组用于拦截 JavaScript 操作的方法。这些方法与 Proxy 处理器对象中的方法相同,可以用来自定义对象的基本操作。
与 Proxy 不同的是,Reflect 不是一个构造函数,不能用来创建对象。它只是一个普通的对象包含了一组静态方法,这些方法与 0bject 对象中的方法类似。
如何使用 Reflect来获取和设置对象的属性:

  const obj={
  		x:1,
  		y:2
  };
  console.log(Reflect.get(obj,'x'));//Reflect.get获取obj的x属性,输出:1
  Reflect.set(obj,'x'3); // 设置obj的x属性
  console.log(obj.x);//输出:3
  //Reflect.get 相当于 obj.x;Reflect.set 相当于 obj.x=3

es6 构造函数super的作用

调用父类的构造函数constructor;

es6 clas static的作用

静态方法或者属性,不用实例化就可以访问;

什么是es6装饰器

在ES6中,装饰器(Decorator)是一种与类(class)相关的语法,用来注释或修改类和类方法。装饰器其实就是一个编译时执行的函数,语法“@函数名”,通常放在类和类方法的定义前面;

for of有什么特点

for…of 是 ES6 中新增的一种循环语句,它用于遍历可迭代对象(例如数组、字符串、 Map、set 等)中的每个元素。

//使用 for...of循环来遍历数组中的每个元素。在每次迭代中,我们都可以直接访问数组中的元素,而不需要使用索引。
const array =['a', 'b', 'c'];
for(const i of array){
	console.log(i);
}
// a
// b
// c

for in有什么特点

for in 循环通常用来遍历对象的键名。
它不适合用来遍历数组,因为它会遍历数组所有的可枚举属性,包括原型,还会把对象的原型给遍历出来,因此不太建议使用for in遍历数组。

promise

promise.all的特点是什么?

同时执行promise,需要等待全部完成;

promise.race的特点是什么?

同时执行Promise,最快的执行完就好,其他的取消;

promise有几种状态

Promise 对象有三种状态:pending(等待中)、fulfilled(已成功)和rejected(已失败)。
一旦一个 Promise 对象的状态从pending 变为了另一种状态,它就不能再改变状态了。

  1. 当一个 Promise 对象被创建时,它的状态是 pending;
  2. 当它被解决时,它的状态变为fulfilled。
  3. 当它被拒绝时,"它的状态变为 rejected。

怎么理解浏览器的宏任务和微任务

在JavaScript引擎中,任务被分为两种类型:宏任务和微任务。

  • 宏任务是由浏览器规定的,例如DOM渲染后触发的setTimeout和setInterval。
  • 微任务是由ES6语法规定的,例如Promise中的then方法。
  • 事件循环又叫做消息循环,是浏览器渲染主线程的工作方式。
    • Chrome 的源码中,它开启一个不会结束的 for 循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时候将任务加入到队列未尾即可。
    • W3C 官方的解释,每个任务有不同的类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。
    • 不同任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪一个队列的任务。
    • 浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先调度执行。
  • 整体代码的执行顺序:
    • 先把所有的代码直接推入执行栈;
    • 一条一条从上往下执行 遇到同步代码直接执行掉 遇到异步代码 也只是"打开";
    • 直到当前执行栈清空;(从上到下,先同步,异步的先放入队列等待执行栈中同步执行完毕即当前执行栈清空);
    • 清空之后线程会去任务队列里看是否有待执行的任务,先查看微任务队列如果有就拿到执行栈里执行执行掉再看微任务队列直到微任务队列清空;
    • 再去看宏任务队列 把宏任务队列中的第一个函数拿到执行栈里执行 执行完之后再去看微任务队列;
    • 重复这个过程直到所有代码都执行完毕 js线程进入闲置状态;
  • 总结:执行代码从上到下依次先同步再异步,异步的先微任务再到宏任务;

console.log是同步还是异步的?

console.log是同步的,与异步完全无关。
chrome的控制台出于对性能的考虑,对于引用类型的数据读取存在延迟。
控制台默认值读取第一层数据,当你点击展开时,才会重新去堆内存中读取属性值和下一层的数据。

weakmap的作用?

weakMap是 ES6 中新增的一种集合类型,叫做“弱映射",由于他的键引用的对象是弱引用,键所指向的对象可以被垃圾回收,可以防止内存泄露。

es6 map数据结构的特点

  1. Map是键值对的集合,键值不限于字符串,可以是任意数据类型,解决javascript的对象只能用字符串作为键的问题;
  2. Map通过set方法添加,通过get方法来获取成员;
  3. Map也是构造函数,也可以接受一个数组作为参数,但该数组的成员也必须是一个个表示键值对的数组。例如:[[“name",“Alvin"],[“age”,18]];
  4. 不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当做Map构造函数的参数。包括Set和Map本身。
  5. 如果对同一个键多次赋值,后面的值将覆盖前面的值;
  6. 如果读取一个不存在的键则返回undefined;
  7. 只有对同一对象的引用,Map才会视为同一个键。也就是说两个对象的内存地址是一样的,才会被视为是同一个键。(Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键)
  8. Map的键值是一个基本类型(数字,字符串,布尔等),则只要两个值严格相等(类型和值都相等)才算是同一个键,如0和-0就是同一键,而true和"true"则是两个键;
  9. undefined 和 null是两个不同的键;
  10. NaN在Map中被视为同一个键;

es6 set数据结构的特点

  1. 是一个构造函数;
  2. 类数组,元素唯一、没有重复;(Set 对象是一个类数组对象,它长得就很像数组。Set 对象存储的值是不重复的,所以我们通常使用它来实现数组去重。)
  3. Set 对象存储的数据不是键值对的形式,而且它可以存储任何类型的数据。

柯里化

柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。
如:一个接收两个参数的函数可以被柯里化为只接收一个参数的函数,该函数返回一个新函数,该新函数接收第二个参数并返回结果(更好的组合和重用):

function add(x){
	return function(y){
		return x+y;
	};
}
const add1=add(1); // 返回一个新的函数
console.log(add1(3)); // 4

JS有哪些数据类型

Javascript一共有8种数据类型;

  1. 基本数据类型有7种:Number、String、Boolean、 Null、Undefind、Symbol(es6新增,表示独一无二的值)、BigInt七种类型,基本数据类型是按值访问的,因为可以直接操作DOM。
  2. 一种引用类型:0bject(包含function、Array、Date);

为什么0.1 +0.2不是0.3

因为计算机硬件存储数据时,是以二进制(10101010)形式进行存储的。所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和。
由于在计算机的角度计算的是二进制而不是十进制,所以在计算的时候先将十进制转换成二进制,而在转换中部分的十进制的浮点数会变成二进制的无限不循环小数。这样再进行运算就会不准确。
解决思路:

  1. 转成整数在做除法运算;
  2. 转成字符串自己实现相加逻辑;

new

  1. 创建一个新的空对象。
  2. 将新对象的_proto_属性指向构造函数的prototype属性。
  3. 执行构造函数,并将新创建的空对象作为this的值。
  4. 如果构造函数返回一个对象,则返回该对象。否则,返回新创建的对象。

typeof 和instanceOf区别?

  1. instanceof:用于检测一个对象是否属于某个类或者其子类,返回一个布尔值。
  2. typeof:用于检测一个值的数据类型,返回一个字符串,该字符串表示该值的数据类型。
    3.注意:typeof null返回的是"object";

事件冒泡

  1. 事件执行过程是先捕获再冒泡的;
  2. 事件冒泡指:当 DOM 元素触发某个事件(如 click、click)时,该事件会从触发事件的「目标元素」开始,逐级向上传播到其父元素、祖先元素,直到 document/window 对象。
  3. 事件流完整流程(冒泡是核心阶段):
    1. 捕获阶段(Capture Phase):事件从 window 向下传播到目标元素(默认不触发,需手动开启);
    2. 目标阶段(Target Phase):事件到达触发的目标元素,执行目标元素的事件处理函数;
    3. 冒泡阶段(Bubbling Phase):事件从目标元素向上传播到 window(默认触发,也是最常用的阶段)。
  4. 实战场景:
    • 事件委托:减少事件绑定数量,新增 / 删除 li 无需重新绑定事件;
    • 阻止冒泡(解决冒泡的副作用):
      • 通过event.stopPropagation()阻止冒泡,事件不再向上传播;
      • event.stopImmediatePropagation()还会阻止当前元素的其他事件处理函数执行;
    • 区分捕获 / 冒泡阶段(手动控制):
      • addEventListener第三个参数设为true,可在捕获阶段触发事件(默认false,冒泡阶段触发)
    // 阻止冒泡
    <!-- DOM结构:div → p → span(嵌套) -->
    <div id="parent">
    	<p id="child">
    		<span id="target">点击我</span>
    	</p>
    </div>
    <script>
    // 给span(目标元素)的事件添加阻止冒泡
    document.getElementById('target').addEventListener('click', (e) => {
    	e.stopPropagation(); // 阻止事件向上冒泡
    	console.log('触发span(目标元素),不再冒泡');
    });
    
    // 父元素div在捕获阶段触发
    document.getElementById('parent').addEventListener('click', () => {
    	console.log('div(捕获阶段)');
    }, true);
    </script>
    

注意

stopPropagation 和 preventDefault区别

  1. e.stopPropagation():阻止事件传播(冒泡 / 捕获),不影响默认行为(如 a 标签跳转);
  2. e.preventDefault():阻止事件默认行为,不影响冒泡(如阻止 a 标签跳转,但事件仍会冒泡到父元素)。

无冒泡的事件:

focus、blur、mouseenter、mouseleave 等事件无冒泡,需用focusin/focusout、mouseover/mouseout替代(后者支持冒泡);

事件委托

  1. 将事件绑定到父级dom。利用冒泡,可将子元素的事件绑定到父元素上(减少事件绑定数量,优化性能)
  2. 根据event.target获取点击的元素。
  3. 子元素多的情况下,只用绑定一次事件,提升性能;
<ul id="list">
  <li>选项1</li>
  <li>选项2</li>
  <li>选项3</li>
</ul>

<script>
// 仅绑定1次事件到父元素,处理所有子元素的点击
document.getElementById('list').addEventListener('click', (e) => {
  // e.target是实际点击的li元素
  if (e.target.tagName === 'LI') {
    console.log('点击了:', e.target.textContent);
  }
});
</script>

split和join的区别?

  1. split可以将字符串以某个字符分割,转成数组;
  2. join可以将数组以某个字符拼接在一起;

堆和栈有什么区别

基本数据类型存在栈中,对象在堆
对象的引用在栈,寻找对象先在栈中取到对象的引用地址,然后根据这个地址找到堆的地址。

怎么实现深拷贝?

  1. 递归调用(函数,数组,Symbol,循环引用);
  2. JSON.stringify + JSON.parse;(一些特殊的和函数不能被拷贝)
  3. 第三方插件lodash.cloneDeep;

怎么实现浅拷贝

  1. Object.assign()(一维深拷贝,多维浅拷贝);
  2. 扩展运算符(对象一维深拷贝,多维浅拷贝);
  3. 数组方法实现数组浅拷贝slice,concat(这两个方法不会改变原数组,里面两个参数都不传就可以实现数组的浅拷贝);

闭包(Closure)

闭包的官方定义:当一个内部函数被其外部函数之外的变量引用时,就形成了闭包;闭包会保留对外部函数作用域的访问权,即使外部函数已经执行完毕。

// 外层函数
function outer() {
  const outerVar = '我是外层变量'; // 外层函数的局部变量
  
  // 内层函数(未执行,被返回)
  function inner() {
    console.log(outerVar); // 内层函数访问外层变量
  }

  return inner; // 返回内层函数,被外部引用
}

// 执行外层函数,接收返回的内层函数
const innerFunc = outer(); 
// 此时outer已执行完毕,但innerFunc仍能访问outer的变量
innerFunc(); // 输出:我是外层变量 → 形成闭包

闭包的作用

  1. 延长作用域链;
  2. 垃圾回收(GC)的例外:如果函数内的内层函数被外部引用,外层函数的作用域会被闭包 “保留”,变量不会被销毁,直到内层函数被销毁。
  3. 实现模块化;

闭包的核心使用场景

  1. 保存私有变量(模块化基础),避免变量污染全局:
    // 封装计数器,变量count仅能通过方法修改,不暴露到全局
    function createCounter() {
    	let count = 0; // 私有变量,外部无法直接访问
    	return {
    		increment: () => count++, // 闭包访问count
    		decrement: () => count--,
    		getCount: () => count
    	};
    	}
    
    const counter = createCounter();
    counter.increment(); // 0+1=1
    counter.increment(); // 1+1=2
    console.log(counter.getCount()); // 输出2
    console.log(window.count); // 输出undefined(变量私有)
    
  2. 事件处理 / 定时器中保留上下文:解决循环绑定事件、定时器中变量被覆盖的问题
    // 反例:循环绑定事件,变量i被覆盖(无闭包)
    for (var i = 0; i < 3; i++) {
    	setTimeout(() => {
    		console.log(i); // 输出3、3、3(循环结束后i=3)
    	 }, 1000);
    }
    
    // 正例:闭包保留每次循环的i值
    for (var i = 0; i < 3; i++) {
    	(function (j) { // 立即执行函数(IIFE)创建闭包
    		setTimeout(() => {
      			console.log(j); // 输出0、1、2
    		}, 1000);
     	})(i);
    }
    
    1. 函数柯里化(参数复用),通过闭包缓存已传入的参数,返回接收剩余参数的新函数。
    // 柯里化示例:计算a+b+c
    function add(a) {
    	return function (b) {
    		return function (c) {
      			return a + b + c;
    	 	};
    	 };
    }
    const add1 = add(1); // 缓存a=1
    const add1And2 = add1(2); // 缓存b=2
    console.log(add1And2(3)); // 输出6(a=1,b=2,c=3)
    
    1. 防抖 / 节流函数(前端性能优化),防抖(debounce)、节流(throttle)的核心是用闭包保存定时器 ID、触发时间等状态。
    // 防抖函数(闭包保存timer)
    function debounce(fn, delay) {
    	let timer = null; // 闭包保留timer,多次调用共用
    	return function (...args) {
    		clearTimeout(timer);
    		 timer = setTimeout(() => {
      		fn.apply(this, args);
    		}, delay);
    	 };
    }
    
    // 使用:输入框搜索防抖
    const input = document.querySelector('input');
    input.addEventListener('input', debounce(() => {
    	console.log('搜索:', input.value);
    }, 500));
    

闭包优缺点

优点缺点
私有化变量,避免全局污染保留外层作用域,变量不销毁,长期占用内存
保存函数执行上下文,复用状态内存泄漏风险(未及时释放闭包引用)
实现模块化、柯里化等高级功能过度使用会导致内存占用过高,影响性能

内存泄漏解决

1)不再使用闭包时,手动解除引用(赋值为 null);
2)避免闭包引用大型对象 / DOM 元素(如 window、document)。

encodeURL和decodeURL的作用

  1. encodeURI 函数主要用来对URL进行转码 它默认采用的是UTF-8编码;
  2. decodeURI用来对encodeURI编码过的URL进行解码;
  3. 使用场景:通过URL传中文参数时,服务端后台获取的值往往会出现乱码;
    • encodeURI和decodeURI是成对来使用的,在浏览器地址栏中有中文字符的话,可能会出现不可预期的错误,所以可以用encodeURI把非英文字符转化为英文编码,decodeURI可以用来把字符还原回来。
  4. 也尝尝配合加解密一起;

window.onload和document.Ready的区别?

  1. window.onload是在dom文档树加载完和所有文件加载完之后执行一个函数;
  2. jquery 中有 $().ready(function),在 dom 文档树加载完之后执行一个函数(注意,这里面的文档树加载完不代表全部文件加载完)。
  3. $(document).ready 要比 window.onload 先执行(dom树构建出来就加载);
  4. window.onload 只能出来一次,$(document).ready 可以出现多次;

xhr对象如何发起一个请求?

XMLHttpRequest(XHR)对象用于与服务器交互。可以在不刷新页面的情况下请求特定 URL,获取数据。

	var xhr = new XMLHttpRequest();
	xhr.open('POST','http://example.com',true);
	xhr.setRequestHeader('content-Type','application/json');
	xhr.onreadystatechange = function(){
		if(xhr.readyState===4 && xhr.status===200){
			console.log(xhr.responseText);
		}
	}
	xhr.send(JSON.stringify({key:'value'}));
	var xhr = new XMLHttpRequest();
	xhr.open('GET','http://example.com',true);
	xhr.onreadystatechange = function(){
		if(xhr.readyState===4 && xhr.status===200){
			console.log(xhr.responseText);
		}
	}
	xhr.send();

request.readyStatus一共有5中状态

  1. 0-(未初始化)还没有调用send()方法;
  2. 1-(载入)已调用send()方法,正在发送请求;
  3. 2-(载入完成)send()方法执行完成,已经接收到全部响应内容;
  4. 3-(交互)正在解析响应内容;
  5. 4-(完成)响应内容解析完成,可以在客户端调用了;
    request.status同HTTP的状态

什么是可迭代对象?

  1. 编程中可迭代对象是指可以通过循环遍历其所有元素的对象。
  2. js中可迭代(Iterable)对象是数组的泛化。可迭代对象不一定是数组,数组一定是可迭代对象。
  3. 每个可迭代对象必然包含一个 [Symbol.iterator]方法属性; 字符串也是可迭代对象。

如何判断一个对象是不是数组?

  1. 从构造函数入手,obj instanceof typeof 和 instanceof 都可以用来判断变量,typeof方法返回一个字符串,来表示数据的类型;
    • typeof:返回一个字符串,来表示数据的类型; 但数组、正则、日期、对象的typeof返回值都是object;
    • instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的prototype 属性,返回一个布尔值。
  2. 从原型入手,Array.prototype.isPrototypeOf(obj);利用isPrototype0f()方法,判定Array是不是在obj的原型链中,如果是,则返回true,否则false。
  3. Array.isArray()方法;
  4. 根据对象的class属性(类属性),跨原型链调用toString()方法;Object.prototype.toString.call()

Parseint有几个参数?

  1. parseInt()函数可解析一个字符串,并返回一个整数。
  2. parseInt(string,radix)string 必需,要被解析的字符串。
  3. radix 可选,表示要解析的数字的基数,该值介于2~36 之间。

什么是 TypedArray?

TypedArray(类型化数组)对象描述了一个底层的二进制数据缓冲区的类数组视图;
TypedArray 定义了如何访问底层的 ArrayBuffer;

尾递归优化

// 每次递归都会创建一个新的栈帧,直到栈溢出。
function factorial(n){
	if(n === 0){
		return 1;
	}else {
		return n * factorial(n-1); // 递归调用,当数据很大时内存占用过大
	}
}
factorial(5);// 120

// 优化后
// 每 次递归都会更新参数,而不是创建新的帧。这样可以避免栈溢出的问题。
function factorial(n,acc=1){
	if(n === 0){
		return acc;
	}else{
		return factorial(n-1,n * acc);
	}
}
factorial(5);// 120

JIT(Just in time)有了解吗?

  • 在传统的编译语言里,比如JAVA、Go等,是提前编译的,提前编译的三大过程:

    1. 分词/词法分析(Tokenizing/Lexing);
    2. 解析/语法分析(Parsing);
    3. 机器代码生成;
  • 但是JS是脚本语言,JS是一边编译一边执行的,所以JS会复杂很多:

    1. 在 语法分析和代码生成阶段有特定的步骤来对运行性能进行优化,包括对余元素进行优化;
    2. 大部分情况下JS编译发生在代码执行前的几微秒(甚至更短!)的时间内。
    3. 任何 JavaScript 代码片段在执行前都要进行编译(通常就在执行前)。因此,JavaScript编译器首先会对 var a=2;这段程序进行编译,然后做好执行它的准备,并且 通常马上就会执行它。

defer和async的区别是什么?

  1. defer要等到整个页面正常渲染结束,才会执行;
  2. async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续染。
  3. defer是"渲染完再执行”,async是"下载完就执行”.
  4. ,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。

commonjs和amd、cmd的区别?

  1. commonjs是nodejs模块化规范;
  2. Amd、cmd和commonjs比较像,已经过时,很少用了;

ES6 Module

  1. 静态编译:在编译的时候就能确定依赖关系,以及输入和输出的变量;
  2. 用于:目前浏览器端的默认标准;

CommonJs和ES6 Module区别

  1. Common]s 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用;
  2. Common]s 模块是运行时加载,ES6模块是编译时输出接口;
  3. Common]S 模块的require()是同步加载模块,ES6模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

async await的原理

  1. async/await 会被编译成 ES5 或者 ES6 的 Promise 对象。
  2. async 函数会被编译成一个返回 Promise 对象的函数,而 await 表达式会被编译成 then 方法的调用。
  3. async/await 的实现依赖于编译器类似yield return 的功能,也就是把原本一个完整的函数拆成状态机。
 	async function foo(){
 		const result=await bar();
 		console.log(result);
 	}
 	// 编译成
   function foo(){
   	return new Promise((resolve, reject) => {
   		bar().then((result) => {
   		console.log(result);
   		resolve();
   		}).catch(reject)
   	})
   }
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐