56.不使用promise能否把请求数据返回出来?
- async/await
- generator函数
- 回调函数(类似于react中的子父传值)
57.async和await
async
和await
是ECMAScript 2017 (ES8)标准引入的新特性,用于简化异步操作。async:用于修饰一个异步操作的函数
,该函数返回一个Promise对象。如果函数中没有返回值,会默认返回一个Promise对象。如果在函数中 return 一个直接量,async 会把这个直接量通过Promise.resolve()
封装成 Promise 对象;- await:用来等待一个异步方法执行完成。后面await如果是一个 Promise 对象,返回该Promise执行成功或者失败的结果。如果不是 Promise 对象,就直接返回对应的值。注意await 只能出现在 async 函数中,await会阻塞进程。
58.原型和原型链
- 每个对象都有一个prototype属性,表示对象的原型(prototype也是一个对象)。
- prototype作为对象的内部属性,是不能被直接访问的,但是可以通过__proto__来访问。
- 原型链,当访问对象的属性或方法时,首先对象会从自身去找,找不到就会往原型(prototype)中去找,如果原型(prototype)中找不到,就会往原型后面的原型上去找,这样就形成了链式的结构,称为原型链。
- 原型链的最顶层是Object,在往上就是null。
59.this指向问题
- 全局作用域中的函数:非严格模式下其内部this指向window
- 对象内部的函数:其内部this指向对象本身:
- 构造函数:其内部this指向生成的实例:
- 由apply、call、bind改造的函数:其this指向第一个参数:
- 箭头函数:箭头函数没有自己的this,看其外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window。(函数定义时的this,而不是调用时this)
60.怎么改变this指向?
- call():在改变this指向的同时会调用函数
- apply():在改变this指向的同时会调用函数,第二个参数接收的是一个数组。
- bind():在改变this指向的同时不会调用原来的函数,而使生成并调用一个新的已经改变过this指向的函数
61.new做了什么
- 在内存创建一个新对象
- 把构造函数中this指向新建的对象
- 会在新对象上添加一个__proto__属性,指向函数的原型对象prototype
- 判断函数返回值,如果值是引用类型就直接返回值;否则返回this(创建的新对象)
62.构造函数
- 什么是构造函数:JS中的任何一个普通函数,当用new关键字来调用时,它就是构造函数。构造函数与函数定义无关,与调用方法有关。构造函数一般首字母大写。
- 构造函数的目的:在JavaScript中,构造函数是用来初始化新创建的对象的函数。构造函数的主要目的是在创建对象时初始化对象的属性。
- 构造函数的意义:使用对象字面量创建一系列同一类型的对象时,这些对象可能具有一些相似的属性和方法,此时会产生很多重复的代码,把这些重复性的特征和属性抽象出来,做成构造函数,可以实现代码复用。
- 构造函数的作用:构造新对象,设置对象的属性和方法。创建对象时完成初始化,当我们在new一个对象并传入参数的时候,会自动调用构造函数并完成参数的初始化。
- 常见的构造函数:Object、Array、String、Boolean、Number、Date等。
- 构造函数的this指向:
- 当以函数的形式调用时,this是window
- 当以方法的形式调用时,谁调用方法this就是谁
- 当以构造函数的形式调用时,this就是新创建的那个对象
- 自定义构造函数:
- 首字母大写
- 通过new创建实例对象
- 创建构造函数时,里面的属性和方法前必须加this,this就表示当前运行时的对象
- 返回值
- 不写return,返回一个this对象
- return一个基本数据类型,返回一个this对象
- return一个复杂数据类型,返回一个复杂数据类型,比如对象、数组
63.构造函数和普通函数的区别
- 普通函数是小驼峰的名命方式,而构造函数是大驼峰的名命方式(行业规范)。
- 我们知道普通函数的this指向是指向全局对象的,而构造函数内部的this指向当前对象的实例。
- 使用的方式不同,普通函数直接调用,构造函数必须使用new 来调用,通过 new.target 来判断调用的方式是不是构造函数。
- 任何函数只要使用new操作符调用就是构造函数,而不使用new操作符调用的函数就是普通函数
63.es6class类
- 类(class)是ES6新的基础性语法糖结构,用于创建对象的模板。可以看成构造函数的另一种写法,这种写法可以让对象原型的写法更加清晰、更像面向对象编程的语法而已。
- 类必须使用
new
调用,否则会报错。普通构造函数使用new创建的是实例化对象,不使用new则执行的是普通函数的调用。 - 类的数据类型就是函数,类本身就指向构造函数。
- 函数受函数作用域限制,而类受块作用域限制。
64.constructor
- constructor 方法是一个特殊的方法,用于创建和初始化一个由
class
创建的对象。通过 new 关键字生成对象实例时,自动会调用该方法。 - 一个类只能拥有一个名为"constructor"构造函数,不能出现多个,如果定义了多个"constructor"构造函数,则将抛出 一个SyntaxError错误。
- 如果没有定义"constructor"构造函数,class 会默认添加一个空的"constructor"构造函数。
65.super
- super代表的是父类的构造函数。super可以用来调用父类的属性和方法,也可以用来调用父类的构造函数。
- super继承。
- ES6 class 可以通过
extends
关键字实现继承,同时子类必须在constructor中调用super,否则新建实例时会报错。这是因为子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super
方法,子类就得不到this
对象,只有调用super
之后,才可以使用this
关键字。 - 子类如果没有定义constructor方法,super方法会被默认添加。
- ES6 class 可以通过
- super方法。
super
作为函数调用时,代表父类的构造函数。super
虽然代表了父类的构造函数,但是返回的是子类的实例,即super
内部的this
指的是子类的实例,因此super()
在这里相当于A.prototype.constructor.call(this)
。
- super对象。
- 在普通方法中,指向父类的原型对象。由于
super
指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super
调用的。ES6 规定,在子类普通方法中通过super
调用父类的方法时,方法内部的this
指向当前的子类实例。由于this
指向子类实例,所以如果通过super
对某个属性赋值,这时super
就是this
,赋值的属性会变成子类实例的属性。 - 在静态方法中(statrc修饰的方法),指向父类。如果
super
作为对象,用在静态方法之中,这时super
将指向父类,而不是父类的原型对象。在子类的静态方法中通过super
调用父类的方法时,方法内部的this
指向当前的子类,而不是子类的实例。
- 在普通方法中,指向父类的原型对象。由于
66.构造器constructor为什么要使用super
因为子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super
方法,子类就得不到this
对象,只有调用super
之后,才可以使用this
关键字。
67.继承
- 实例继承:将子构造函数的 prototype 指向父构造函数的一个实例
- 原型继承:将子构造函数的 prototype 指向父构造函数的 prototype
- 构造函数绑定:使用 call 或 apply 方法,将父对象的构造函数绑定在子对象上
- 拷贝继承:如果把父对象的所有属性和方法,拷贝进子对象
- ES6 语法 extends:class ColorPoint extends Point {} 并在子类的构造器中调用super函数
68.什么是js严格模式
严格模式(Strict Mode)是一种在代码中启用的特殊模式,用于提供更严格的语法和错误检查,以改善代码质量和增强安全性。使用严格模式可以帮助大家避免一些常见的错误,并禁用一些不推荐使用的特性。
- 变量必须先声明后使用:在严格模式下,变量必须通过 var、let 或 const 关键字进行声明,否则会抛出 ReferenceError。在非严格模式下,未声明的变量会被隐式创建,并被添加到全局对象(比如浏览器环境中的 window 对象)中。
- 禁止删除变量、函数或函数参数:在严格模式下,使用 delete 操作符删除变量、函数或函数参数会抛出 SyntaxError。
- 禁止对只读属性进行赋值:在严格模式下,对只读属性(通过 const 关键字声明的常量)进行赋值会抛出 TypeError。
- 禁止使用八进制字面量:在严格模式下,以 0 开头的数字会被视为八进制字面量,这在非严格模式下是允许的。严格模式下,使用八进制字面量会抛出 SyntaxError。
- 限制 this 值:在严格模式下,函数内部的 this 值不再是全局对象(比如浏览器环境中的 window 对象),而是undefined,除非通过 call()、apply() 或 bind() 明确指定。
- 禁止使用重复的函数参数名:在严格模式下,函数参数名不能重复。在非严格模式下,重复的函数参数名会被忽略。
- 禁止使用 with 语句:在严格模式下,使用 with 语句会抛出 SyntaxError。with 语句在非严格模式下允许将对象的属性添加到作用域链中,但这被认为是不推荐使用的特性,因为它可能导致代码可读性和性能问题。
- 限制 eval 和 arguments 的赋值:在严格模式下,无法对 eval 和 arguments 进行赋值。在非严格模式下,这种赋值是允许的。
69.怎么阻止表单提交默认行为
- **e.**preventDefault()
- οnsubmit事件中return false
70.js事件流是什么?怎么修改事件传播机制?怎么阻止事件传播?
- 捕获阶段:从外向里依次查找元素
- 目标阶段:从当前事件源本身的操作
- 冒泡阶段:从内到外依次触发相关的行为
- addEventListener事件监听器的第三个参数设置成true捕获false冒泡
- event.stopPropagation() 阻止事件传播
71.dom事件委托原理,有什么优缺点
- 事件委托原理
- 利用事件冒泡机制,将事件监听器设置在其父节点上,通过event.target.nodeName判断是否是子节点来实现控制子节点。
- 优点
- 可以大量节省内存占用,减少事件注册
- 可以实现当新增子对象时,无需再对其进行事件绑定
- 缺点
- 如果把所有事件都用事件代理,可能会出现事件误判
72.栈内存和堆内存
- 在JavaScript中,数据是分为两类存储的:基本类型和对象类型。基本类型值指的是那些保存在栈内存(Stack)中的数据,而对象类型值则被保存在堆内存(Heap)中。
- 栈内存是一种后进先出(LIFO)的数据结构,主要用于存储函数的局部变量、临时数据、书签等。当你创建一个基本类型的变量时,它会被存储在栈内存中,并且占据一块连续的空间。
- 堆内存是用来存储对象的地方,对象可以包含多个值,大小不固定,可以动态地增加或减少。当你创建一个对象类型的变量时,这个对象会被存储在堆内存中。
73.主线程和任务队列
- JavaScript 中的主线程和任务队列是浏览器的 JavaScript 引擎如何工作的基本概念。
- 主线程:主线程是 JavaScript 引擎用来执行执行代码的地方。当 JavaScript 引擎开始执行代码时,程序的主线程就会被创建。
- 任务队列:JavaScript 是单线程的,这意味着它只有一个主线程来执行代码。但是,JavaScript 引擎还有其他的任务队列,如微任务队列和宏任务队列。
74.执行栈和调用栈
执行栈和调用栈通常是指程序在执行过程中的两种不同的数据结构。
- 执行栈(Execution Stack):在JavaScript中,执行栈是用来存储执行上下文(Execution Context)的数据结构。每当一个函数被调用时,就会为这个函数创建一个新的执行上下文并将其推入执行栈。执行栈是后进先出(LIFO)的数据结构。当函数执行完毕,它的执行上下文就会从栈中移除。
- 调用栈(Call Stack):调用栈是一个系统级的数据结构,用于存储一个个正在被执行的函数的地址。当一个函数调用另一个函数时,被调用的函数的地址会被添加到调用栈顶部。当这个函数执行完毕,它的地址会从调用栈顶部移除。在JavaScript中,调用栈是由JavaScript引擎管理的,开发者可以通过错误栈跟踪(stack trace)来查看调用栈的状态。
75.js事件循环机制(底层原理)
因为js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。
整体会把所有代码分为两个部分:'同步任务','异步任务'。所有同步任务都在主线程上执行,形成一个执行栈。主线程之外还存在一个任务队列,专门存放异步任务(宏任务和微任务)。
- 宏任务进入到事件表(Event Table)中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到事件队列(Event Queue)中
- 微任务也会进入到另一个事件表(Event Table)中,并在里面注册回调函数,每当指定的事件完成时,事件表(Event Table)会将这个函数移到事件队列(Event Queue)中
- 整体script作为第一个宏任务进入主线程,当主线程的任务执行完毕,主线程为空时,会检查微任务的事件队列(Event Queue),如果有任务,就会全部执行,如果没有就执行下一个宏任务
- 主线程不断重复上面的步骤,这就是Event Loop事件循环,只要主线程空了,就会去读取"任务队列"。这个过程会不断重复。
76.target 和 currentTarget 区别
- 都是事件对象上的属性
- event.target:返回触发事件的元素
- event.currentTarget:返回绑定事件的元素(相当于事件中this)
77.浮点数精度失真
- 计算机内部使用二进制浮点数表示法,而不是十进制。这种二进制表示法在某些情况下无法准确地表示某些十进制小数,从而导致精度丢失。
- 解决方法
- 保留指定位数的小数
- 获取最大小数位,根据最大小数位乘以10的倍数
- 使用decimal.js
78.import和require的区别
- 使用方式。import用于导入整个模块或模块中的特定部分,使用export default或export命令进行导出;require用于导入整个模块,使用module.exports或exports命令进行导出。
- 加载时机。import是在编译时加载,必须放在文件的开头;require是在运行时加载,可以放在代码的任何位置。
- 解构赋值。import支持解构赋值,可以直接导入模块中导出的值、函数或对象;require进行的是赋值操作,导入的是模块导出的对象。
- 所属规范。import是ES6(ECMAScript 2015)引入的关键字,属于ES模块化语法规范;require是CommonJS规范的一部分,主要用于Node.js环境。
- 动态绑定。import提供静态分析,支持宏和类型检验;require提供动态绑定,更适合服务器或浏览器环境。
- 此外,由于历史原因和兼容性问题,在Node.js中,import语法通常需要通过Babel等工具转码为require语句才能使用。尽管import是ES6标准的一部分,并且在现代JavaScript开发中非常常用,但require仍然被广泛支持,特别是在Node.js社区中。开发者可以根据项目需求和目标平台选择使用import或require。
79.postMessage
-
作用:"跨文档消息传递 ",又称为"窗口间消息传递 "或者"跨域消息传递 "。postMessage方法 允许来自一个文档的脚本可以传递文本消息到另一个文档里的脚本,而不用管是否跨域,可以用这种消息传递技术来实现安全的通信。具体使用场景如下:
- 页面和其打开的新窗口的数据传递
- 页面与嵌套的 iframe 消息传递
- 多窗口之间消息传递
- 跨域数据传递
-
发送消息:otherWindow.postMessage(message, targetOrigin);
- otherWindow:其他窗口的一个引用。比如 iframe 的 contentWindow 属性、执行 window.open 返回的窗口对象等。
- message:要发送的消息。它将会被结构化克隆算法序列化,所以无需自己序列化,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化。
- targetOrigin:"目标域"。URI(包括:协议、主机地址、端口号)。若指定为"*",则表示可以传递给任意窗口,指定为"/",则表示和当前窗口的同源窗口。当为URI时,如果目标窗口的协议、主机地址或端口号这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会发送。
-
接收消息:当调用
postMessage()
方法的时候,目标窗口的Window对象上就会触发一个message
事件。为window添加message事件即可获取postMessage传来的消息。 -
安全问题:
- 如果你不希望从其他网站接收 message,请不要为 message 事件添加任何事件监听器。
- 如果你确实希望从其他网站接收message,请使用使用事件对象中的 origin 和 source 属性验证发件人的身份。
- 当你使用 postMessage 将数据发送到其他窗口时,始终指定精确的域名,而不是 *。