Promise中的错误能否被外层的catch捕获到?
不能,trycatch只能处理同步代码错误
map/forEach循环里async、await为什么会失效
map/forEach
forEach
只支持同步代码。内部使用了while结合callback方式来执行函数,await不会等待callback的执行
js有哪些内置可迭代对象
javascript中的内置可迭代对象有”array“、”map“、”set“、”string"、“typedarray”、”nodelist“和”arguments“:1、array,数组对象;2、map,是一种键值对集合;3、set,是一种集合;4、string,字符串对象;5、typedarray,一种类数组对象;6、nodelist,一种节点列表对象等等。
… 扩展运算符(Spread Operator)究竟是浅复制还是深复制
扩展运算符实现的是浅拷贝(Shallow Copy),它只能复制对象的第一层属性。如果对象的属性值是基本数据类型(如字符串、数字、布尔值等),那么这些属性值会被直接复制。但如果对象的属性值是引用数据类型(如数组、对象等),那么复制的仅仅是这些引用类型数据的引用(内存地址),而不是数据本身。因此,如果修改了这些引用类型数据的内容,那么原始对象和拷贝对象中的对应数据都会发生变化。
使用JSON.stringify()
进行深拷贝有以下几个缺点:
无法处理循环引用:当对象中存在循环引用时,即对象的某个属性指向该对象本身,
JSON.stringify()
会抛出异常。无法拷贝非枚举属性和方法:
JSON.stringify()
只能拷贝对象自身的可枚举属性,并且会忽略函数和Symbol
类型的属性。无法拷贝特殊的对象属性:
JSON.stringify()
不会拷贝对象的原型链上的属性。无法处理日期对象:将日期对象转换为JSON字符串后再使用
JSON.parse()
解析时,日期对象会变成字符串,而不是重新生成日期对象。无法处理正则表达式对象:正则表达式对象在转换为JSON字符串后会变成空对象。
无法处理
undefined
和function
:JSON.stringify()
会将undefined
和函数直接转换为null
。无法处理
Infinity
和NaN
:JSON.stringify()
会将Infinity
和NaN
转换为null
。
将一个接口的所有属性变成可选的
type Partial扒手<T> = {
[P in keyof T]?: T[P]|undefin
}
ts type和interface区别
语法差异:
interface
使用interface
关键字来定义类型,例如:interface Person { name: string; age: number; }
。type
使用type
关键字来定义类型,例如:type Person = { name: string; age: number; }
。对象类型和扩展性:
interface
主要用于描述对象的形状和结构,可以定义对象的属性、方法、索引等,并支持扩展,可以使用extends
关键字继承其他interface
。type
用于创建类型别名,可以给一个类型起一个别名,可以是基本类型、联合类型、交叉类型或任何其他类型。它也支持扩展,可以使用&
操作符表示交叉类型。实现类和抽象类:
interface
可以用于类的实现和抽象类的定义。类可以通过implements
关键字来实现一个或多个interface
,并且必须实现接口中定义的属性和方法。type
不能直接用于类的实现,它主要用于定义复杂的类型别名。联合类型和交叉类型:
type
可以用于创建联合类型和交叉类型。联合类型使用|
操作符表示,表示一个值可以是多个类型之一。交叉类型使用&
操作符表示,表示多个类型的组合。interface
不直接支持联合类型和交叉类型的定义,但可以通过继承其他interface
或结合类型推断进行类似的操作。重复声明和合并:
interface
可以重复声明,支持声明合并,这意味着可以在同一个接口中添加新的成员。type
定义后不能重复声明,不支持声明合并。基本类型别名:
type
能够表示非对象类型,如可以定义基本类型别名,如type StringType = string
。而interface
则只能表示对象类型。总体而言,
interface
主要用于定义对象的形状和结构,支持扩展和类的实现;而type
主要用于创建类型别名,支持联合类型和交叉类型,也支持扩展。
es5和es6的继承有什么区别
ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this)).
ES6的实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。
ES5的继承时通过原型或构造函数机制来实现。
ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。
ES5和ES6继承最大的区别就是在于:
1.ES5先创建子类,在实例化父类并添加到子类this中
2.ES6先创建父类,在实例化子集中通过调用super方法访问父级后,在通过修改this实现继承
es6 es7新特性
ES6 新特性:
箭头函数: 使用
=>
符号定义匿名函数,简化了函数的书写,并改变了函数内部this
的指向。模板字符串: 使用反引号 ` ` 包裹字符串,可以在其中插入变量和表达式,使得字符串的拼接更加方便。
解构赋值: 可以通过解构语法从数组或对象中提取值,赋给变量,使得代码更加简洁和易读。
扩展运算符和剩余参数: 使用
...
符号可以将数组或对象展开为参数序列,也可以用于剩余参数的收集。类和继承: 引入了类(class)语法糖,使得在 JavaScript 中更加容易地实现面向对象的编程,包括类的定义、继承和方法等。
模块化: 引入了模块化的语法,包括
import
和export
关键字,使得 JavaScript 代码可以更好地组织和管理。Promise 对象: 提供了 Promise 对象,用于处理异步操作,使得异步代码更加清晰和可读,并解决了回调地狱的问题。
新的数据结构: 引入了 Map、Set、Symbol 等新的数据结构,丰富了 JavaScript 的内置对象和功能。
ES7 新特性:
Array.prototype.includes: 在数组原型上添加了
includes
方法,用于判断数组是否包含某个值,替代了indexOf
方法。指数操作符: 引入了
**
运算符,用于计算一个数的指数,例如2 ** 3
表示 2 的 3 次方。Async/Await: 引入了 Async/Await 语法糖,用于更加简洁地处理异步操作,基于 Promise 对象的语法糖,使得异步代码更加直观和易于理解。
Object.values/Object.entries: 在 Object 上添加了
Object.values
和Object.entries
方法,用于获取对象的值和键值对数组。String.prototype.padStart/padEnd: 在字符串原型上添加了
padStart
和padEnd
方法,用于在字符串前或后填充指定字符,达到指定的长度。Trailing commas in function parameter lists and calls: 允许在函数参数列表和调用中使用尾随逗号,增加代码的可维护性和可读性。
sse和websocket区别
- 协议:
○ SSE基于HTTP协议。它使用常规的HTTP连接,但是服务器可以在任何时候发送数据给客户端,而不需要客户端明确地请求。
○ WebSocket也是建立在HTTP之上的,但它在客户端和服务器之间创建了一个全双工的通信通道,允许双方随时发送消息。- 连接性:
○ SSE是单向通信,只允许服务器向客户端推送数据。
○ WebSocket是双向通信,允许服务器和客户端之间进行双向数据交换。- 实时性:
○ SSE适用于单向的实时通信,例如服务器向客户端推送实时更新。
○ WebSocket更适用于需要双向实时通信的场景,例如在线聊天应用或在线游戏。- 适用场景:
○ SSE适用于一些简单的实时通知场景,如新闻推送、股票价格更新等。
○ WebSocket更适合复杂的实时应用,如在线游戏、即时聊天和协同编辑。- 兼容性:
○ SSE在现代浏览器中得到了广泛支持,但在某些旧版本浏览器中可能会有限制。
○ WebSocket也在现代浏览器中有很好的支持,但需要注意在某些网络环境下可能会被阻塞。
uuid是什么
UUID(Universally Unique Identifier)是一种唯一标识符,通常用于标识信息的唯一性。UUID是一个128位的数字,通常以32个字符的十六进制字符串表示,例如:"550e8400-e29b-41d4-a716-446655440000"。
UUID的生成规则保证了在理论上不会出现重复的情况,即使在多个系统中生成UUID也不会产生冲突。UUID通常被用于分布式系统中,作为唯一标识来标记数据、实体或者事件,确保全局唯一性。
UUID有不同的版本,常见的有基于时间戳和随机数生成的版本。在实际应用中,UUID经常被用于数据库主键、会话标识、消息队列中的消息ID等场景,以确保数据的唯一性和避免冲突。
多个域名维护中会遇到哪些问题 a.b.c.com/d.b.c.com/e.f.c.com
DNS配置管理:需要确保每个子域名都正确指向相应的服务器IP地址。
SSL证书管理:每个子域名都可能需要单独的SSL证书来保证连接的安全性。
Web服务器配置:各个子域名可能需要不同的虚拟主机配置或者反向代理设置。
代码和资源管理:如果每个子域名都对应不同的网站或应用程序,需要考虑如何管理各个子域名的代码和静态资源。
跨域访问:在前端开发中,可能会涉及到跨域访问问题,需要正确配置CORS(跨域资源共享)。
SEO优化:需要针对每个子域名分别进行SEO优化,确保各个子域名的排名和流量。
监控和日志:需要考虑如何统一监控各个子域名的运行状态,并且对访问日志进行合理管理。
typeof instanceof Object.prototype.toString.call 原理
typeof
:typeof
是一个一元操作符,用来返回一个值的数据类型,它能够返回以下几种结果:
"undefined"
: 如果值是undefined
"boolean"
: 如果值是布尔类型"number"
: 如果值是数字类型"string"
: 如果值是字符串类型"symbol"
: 如果值是 Symbol 类型"object"
: 如果值是对象或null
"function"
: 如果值是函数
instanceof
:instanceof
是一个二元操作符,用来检查构造函数的prototype
属性是否出现在对象的原型链中的任何位置。语法为object instanceof constructor
。如果constructor.prototype
在object
的原型链上,则返回true
,否则返回false
。
Object.prototype.toString.call
:这是一种通用的方式来获取对象的具体类型信息。所有对象都继承自Object
,而Object.prototype.toString
是一个内置方法,用来返回对象的类型。但是,直接使用Object.prototype.toString
时,不能直接传入变量,需要通过call
或apply
来改变this
的指向,从而实现对不同类型数据的准确判断,例如:javascriptCopy Code
Object.prototype.toString.call([]); // "[object Array]" Object.prototype.toString.call({}); // "[object Object]" Object.prototype.toString.call(123); // "[object Number]"
说说微前端的路由设计、主子路由调度通信、子路由设置 basename 会影响多主应用嵌套它,如何解决
微前端的路由设计:在微前端架构中,一种常见的做法是将路由进行分离,每个微前端应用拥有独立的路由配置。主应用可以使用一个统一的路由器(如 single-spa、qiankun 等)来协调不同微前端应用之间的路由切换,并根据路由匹配加载相应的微前端应用。
主子路由调度通信:在微前端架构中,主应用和子应用之间的通信通常通过事件总线、全局状态管理库(如 Redux、Vuex 等)或自定义事件机制来实现。主子路由之间的通信可以通过事件触发和监听的方式来进行,从而实现数据传递和状态同步。
子路由设置 basename 影响多主应用嵌套:当子应用设置了 basename 后,如果多个主应用嵌套这个子应用,可能会导致路由路径匹配出现问题,子应用的路由无法正确映射到对应的主应用上。为解决这个问题,可以考虑以下方法:
- 动态设置 basename:在子应用启动时,根据当前的环境动态设置 basename,以确保子应用在不同环境中都能正确匹配路由。
- 统一管理路由:将所有主子应用的路由配置集中管理,避免出现路由冲突或不匹配的情况。
- 路由适配处理:在主应用中对子应用的路由进行适配处理,可以通过路由拦截等方式来动态修改路由路径,以确保子应用能够正确匹配到路由
hash 和 history 优缺点
Hash 路由:
优点:
- 兼容性好:Hash 路由可以兼容所有浏览器,包括旧版本浏览器。
- 简单易用:使用 Hash 路由不需要服务器端配置,只需在前端进行处理即可。
- 防止刷新页面时出现 404 错误:由于 Hash 路由会将路由信息保存在 URL 的哈希部分,因此刷新页面时不会向服务器发送请求,避免了出现 404 错误。
缺点:
- URL 不够美观:Hash 路由的 URL 中会带有
#
符号,不够友好和美观。- SEO 不友好:搜索引擎对于 Hash 路由的内容抓取能力较弱,不利于 SEO 优化。
History 路由:
优点:
- URL 美观:History 路由的 URL 不带
#
符号,更加美观和清晰。- SEO 友好:History 路由的内容更容易被搜索引擎抓取和索引,有利于 SEO 优化。
- 用户体验好:使用 History 路由可以提升用户体验,URL 更符合用户的习惯。
缺点:
- 需要服务器端支持:History 路由需要服务器端配置来支持路由的正确映射,需要额外的工作。
- 兼容性较差:部分旧版本浏览器和服务器可能不支持 History API,需要特殊处理。
monorepo 优缺点
单一代码仓库(Monorepo)是一种软件开发模式,其中所有项目或组件都存储在同一个版本控制存储库中。这种模式有许多优点和缺点,下面我将列举一些常见的优缺点:
优点:
代码共享和重用:所有项目共享同一个代码仓库,便于代码的共享和重用,减少重复工作。
统一构建和部署:所有项目可以使用相同的构建和部署流程,简化了整体的开发和部署过程。
依赖管理:统一管理依赖项,可以确保版本一致性,避免不同项目之间的依赖冲突。
跨项目协作:开发人员更容易在不同项目之间共享代码、协作开发,促进团队协作和沟通。
更好的版本控制:更容易跟踪和管理不同项目之间的变化,提高代码可维护性。
缺点:
构建时间增加:随着项目数量的增加,整体构建时间可能会增加,对开发效率产生影响。
复杂性增加:管理多个项目在同一个仓库中可能增加复杂性,特别是在配置文件、依赖管理等方面。
代码耦合:不同项目共享同一个仓库可能导致代码之间的耦合度增加,一处改动可能影响其他项目。
权限管理:难以实现细粒度的权限管理,特别是对不同项目的权限控制。
团队协作挑战:需要团队成员之间更密切的协作和沟通,以避免对其他项目造成负面影响。
pnpm、yarn、npm 的各个版本分别解决了哪些问题、优缺点
npm
问题解决:
- npm 是 Node.js 官方的包管理工具,最初用于解决 JavaScript 包的依赖管理和版本控制问题。
优点:
- 随着 Node.js 的发展,npm 已经成为了 JavaScript 生态系统中最常用的包管理工具之一。
- 社区支持广泛,拥有大量的开源包和模块。
缺点:
- npm 在安装依赖时会产生大量的冗余文件,占用磁盘空间较大。
- npm 安装依赖时采用的硬链接方式,可能导致依赖包之间的共享问题。
Yarn
问题解决:
- Yarn 的出现解决了 npm 安装速度慢、安装包不稳定等问题,提供了更快的安装速度和更可靠的依赖管理。
优点:
- Yarn 采用并行安装、缓存等机制,大大提高了包的安装速度。
- Yarn 通过锁定文件(
yarn.lock
)来确保在不同环境下安装的包的版本一致性。缺点:
- Yarn 的缓存机制可能占用大量磁盘空间。
- Yarn 对于一些特殊情况下的依赖解析可能存在问题。
pnpm
问题解决:
- pnpm 旨在解决 npm 和 Yarn 对磁盘空间占用的问题,以及安装速度等问题。
优点:
- pnpm 通过符号链接技术,实现了全局依赖的共享,有效减少了磁盘占用空间。
- pnpm 采用类似于 Yarn 的缓存机制,提高了包的安装速度。
缺点:
- pnpm 在一些复杂的依赖情况下可能存在一些兼容性问题。
- 社区使用相对较少,可能在某些场景下缺乏足够的支持。
TS 联合类型 字面量类型
联合类型允许一个变量具有多种不同类型中的一种。使用
|
符号来分隔多个类型,表示该变量可以是这些类型中的任意一种。let myVar: string | number;
字面量类型允许将一个具体的值作为类型,而不仅仅是类型范围。可以使用字符串字面量、数字字面量等来定义字面量类型。
let status: "success" | "error";
ES6 迭代器、生成器有什么作用
迭代器(Iterator):
- 迭代器提供了一种统一的方式来遍历不同的数据结构,比如数组、对象、字符串等。通过调用迭代器对象的
next()
方法,可以依次访问数据结构中的每个元素。- 迭代器使得遍历操作变得更加灵活和方便,可以在循环中暂停、恢复和终止遍历。
- 例如,在使用
for...of
循环遍历数组时,实际上就是在使用数组的迭代器。生成器(Generator):
- 生成器是一种特殊的函数,可以通过
function*
关键字定义。生成器函数执行后会返回一个迭代器对象,通过该对象可以逐步执行生成器函数中的代码块。- 生成器函数中可以使用
yield
关键字来暂停函数执行,并将值返回给调用者。每次调用生成器的next()
方法都会执行到下一个yield
语句为止。- 生成器可以简化异步编程,使得代码更易读、更易维护。例如,可以使用生成器函数来简化 Promise 的链式调用,或者处理异步任务的串行执行。
JWT是什么
JWT(JSON Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式用于在各方之间安全地传递信息。JWT通常被用来在用户和服务器之间传递身份信息,以便在跨域和跨系统的环境下进行认证和授权。
一个JWT实际上就是一个以 JSON 对象表示的安全令牌,它由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。这三部分通常是用点号(.)连接起来的字符串。
头部(Header):头部通常包含了两部分信息,分别是令牌的类型(比如JWT)和所使用的加密算法(比如HMAC SHA256或RSA等)。
载荷(Payload):载荷包含了需要传递的信息,其中可以包括一些预定义的声明(比如用户ID、过期时间等),也可以包含一些自定义的声明。
签名(Signature):签名是对头部和载荷进行加密生成的,它可以确保令牌在传输过程中没有被篡改。
JWT的工作流程通常如下:
- 用户登录后,服务器验证用户身份,并生成一个包含用户信息的JWT。
- 服务器将这个JWT返回给客户端,客户端通常会将JWT保存在本地,比如localStorage或者cookie中。
- 客户端在后续的请求中携带这个JWT。
- 服务器在接收到带有JWT的请求时,会验证JWT的有效性和完整性,然后使用其中的信息来完成认证和授权等操作。
JWT具有以下特点:
- 自包含:JWT本身包含了所有需要的信息,避免了服务器端存储会话信息的开销。
- 跨域:JWT可以在不同的域名间传递,适用于跨域场景。
- 安全性:通过签名机制,可以确保令牌在传输过程中不被篡改。
总的来说,JWT是一种在网络环境中用于安全传递信息的标准化方案,它在用户认证和授权方面提供了一种有效的解决方案。
本地存储、第三方 cookie、用户身份标识实现流程
本地存储:
- 当用户首次访问网站时,生成一个唯一的用户标识(比如UUID)。
- 将这个用户标识存储在本地,可以选择使用 localStorage 或者 sessionStorage 等浏览器提供的本地存储方式。
- 在用户后续的访问中,通过读取本地存储中的用户标识来识别用户。
第三方 Cookie:
- 在用户首次访问网站时,网站可以向第三方服务商发送请求,获取一个用于跟踪用户的 Cookie。
- 第三方 Cookie 通常包含一个唯一的标识符,可以用来标识用户并跨多个网站进行跟踪。
- 网站在接收到第三方 Cookie 后,将其存储在用户的浏览器中。
用户身份标识:
- 用户身份标识通常是一个能够唯一标识用户身份的信息,比如用户名、邮箱、手机号等。
- 网站可以要求用户在注册或登录时输入这些身份信息,并将其关联到用户的唯一标识(本地存储或者第三方 Cookie)上。
- 当用户再次访问网站时,网站可以通过用户身份标识来获取用户相关信息,实现个性化服务或跟踪用户行为。
白板实现一下单例模式,为什么单例模式用 class 的方式实现更合适
我们使用单例模式时,我们希望确保一个类只有一个实例,并且提供一个全局访问点。
var Singleton = (function () { var instance; function createInstance() { // 在这里创建单例对象 return { someProperty: 'someValue' }; } return { getInstance: function () { if (!instance) { instance = createInstance(); } return instance; } }; })(); // 测试单例模式 var s1 = Singleton.getInstance(); var s2 = Singleton.getInstance(); console.log(s1 === s2); // 输出 true,表明 s1 和 s2 是同一个实例
在这个示例中,通过立即执行函数创建了一个闭包,内部定义了
instance
变量和createInstance
函数,通过getInstance
方法来获取单例实例。确保同一个实例只被创建一次。为什么使用函数方式实现单例模式在 JavaScript 中更合适呢?
简洁性:使用函数方式实现单例模式比较简洁明了,不需要额外的类定义,更符合 JavaScript 的语法特点。
闭包特性:闭包能够有效地隐藏内部变量,保护单例实例不受外部直接访问,提高了封装性和安全性。
灵活性:函数方式实现的单例模式也很灵活,可以在内部定义更多的私有方法和属性,满足不同业务需求。
装饰器模式,使用场景
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许你在运行时为对象添加新功能,同时不改变其结构。装饰器模式通过创建包装对象来实现这一点,这些包装对象包含原始对象,并实现了与原始对象相同的接口,但可以在调用原始对象的方法之前或之后执行额外的操作。
使用场景:
动态地给对象添加功能:当需要在不修改现有代码的情况下动态地添加功能或责任时,装饰器模式非常有用。通过组合多个装饰器,可以灵活地给对象添加多个功能。
避免子类爆炸:使用装饰器模式可以避免创建大量子类来实现各种组合方式的功能扩展,从而减少类的数量,提高代码灵活性和可维护性。
开放-关闭原则:装饰器模式符合开放-关闭原则,即对扩展开放,对修改关闭。通过装饰器模式,可以动态地添加或移除功能,而无需修改现有代码。
动态组合对象:装饰器模式可以通过组合不同的装饰器对象来动态地构建具有不同功能组合的对象。
例子:在实际开发中,比如在Web开发中,可以使用装饰器模式来实现日志记录、性能监控、权限控制等功能的动态添加;在GUI开发中,可以利用装饰器模式来动态地添加边框、滚动条等装饰效果;在游戏开发中,可以使用装饰器模式来动态添加武器、道具等功能。
发布订阅和观察者区别
发布-订阅模式是一种消息范例,其中发送者(发布者)不会直接将消息发送给特定的接收者(订阅者),而是通过一个称为"主题"或"通道"的中介来进行广播。订阅者可以向这个主题注册自己的兴趣,一旦发布者发布消息到主题上,所有对该主题感兴趣的订阅者都会收到相应的消息。这种模式类似于广播,发布者和订阅者之间不存在直接的联系。
观察者模式是一种对象行为型模式,它定义了一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都会得到通知并自动更新。在观察者模式中,被观察者(也称为主题或目标)维护了一个包含所有观察者的列表,并提供注册和移除观察者的方法。当被观察者的状态变化时,它会主动通知所有注册的观察者。
因此,两者的主要区别在于消息传递的方式:发布-订阅模式使用中介来进行消息广播,而观察者模式则是被观察者直接通知观察者。
如何确保你的构造函数只能被new调用,而不能被普通调用?
- 借助
instanceof
和new
绑定的原理,适用于低版本浏览器,使用instanceof
操作符来检查this
是否是当前构造函数的实例- 借助
new.target
属性,可与class
配合定义抽象类- 面向对象编程使用
ES6 class
——最佳方案
先捕获还是先冒泡
先捕获后冒泡 是指事件传播的顺序:事件首先从最外层的祖先元素向目标元素传播(捕获阶段),然后再从目标元素向最外层的祖先元素传播(冒泡阶段)。
try...catch 可以捕获到异步代码中的错误吗?
不能。
setTimeout是一个异步函数,它的回调函数会在指定的延时后被放入事件队列,等待当前执行栈清空后才执行。因此,当setTimeout的回调函数执行并抛出错误时,try...catch已经执行完毕,无法捕捉到异步回调中的错误。
对于异步代码,需要结合 Promise 、async/await 或者事件监听器等机制来处理错误。
用过哪些设计模式 ?
1.工厂模式:
优点:
1)工厂模式解决了重复实例化的问题
2)消除对象间的耦合,通过使用工程方法而不是new关键字。
缺点:识别问题,
不能识别对象类型。
2.构造函数模式:优点:
1) 使用构造函数的方法,即解决了重复实例化的问题,也解决了对象识别的问题
2) 该模式与工厂模式的不同之处在于直接将属性和方法赋值给 this 对象缺点:构造函数中的每个方法都要在实例上重新创建一遍
3.原型模式 (Prototype)
优点:解决了构造函数中的每个方法都要在实例上重新创建一遍的问题
缺点:如果在原型中定义属性,将被所有实例共享
4.观察者模式
属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
5.发布订阅模式
订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。
观察者模式和发布订阅模式的区别:
调度方式不同:观察者模式只有两个,一个是观察者一个是被观察者,由具体目标调度。发布订阅模式不一样,发布订阅模式还有一个中间层,由调度中心调度,发布订阅模式的实现是,发布者通知给中间层 => 中层接受并通知订阅者 => 订阅者收到通知并发生变化
6.桥接模式
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
其实就是函数的封装,比如要对某个DOM元素添加color
和backgroundColor
,可以封装个changeColor
函数,这样可以在多个相似逻辑中提升智商...
数组去重方式(最少三个)
set,map,indexOf,for循环
JavaScript常见的数组去重_for (let i = 0; i < pieces.length; i++) { if (piec-CSDN博客
箭头函数与普通函数的区别 ?
1.函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象
2.不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出出个错误
3.不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 Rest 参数代替
4.不可以使用 yield 命令,因此箭头函数不能当作 Generator 函数
callee和caller的区别
caller返回一个调用当前函数的函数的引用
使用这个属性要注意:
1 这个属性只有当函数在执行时才有用
2 如果在javascript程序中,函数是由顶层调用的,则返回nullvar a = function() {
alert(a.caller);
}
var b = function() {
a();
}
b();
//function() { a() }callee返回正在执行的函数本身的引用,它是arguments的一个属性
使用callee时要注意:
1 这个属性只有在函数执行时才有效
2 它有一个length属性,可以用来获得形参的个数,因此可以用来比较形参和实参个数是否一致,即比较arguments.length是否等于arguments.callee.length
3 它可以用来递归匿名函数。var a = function() {
alert(arguments.callee);
}
var b = function() {
a();
}
b();// function() {alert(arguments.callee);}
作用:callee和caller的作用与区别_callee 与caller-CSDN博客
JS节点操作
访问元素:getElementById()、getElementByTagName()
遍历元素:使用 parentNode、nextSibling、previousSibling、firstChild 和 lastChild 属性可以遍历文档树中任意类型节点,包括空字符(文本节点)。
创建元素:var element = document.getElement("tagName");
复制节点:cloneNode()
插入节点: appendChild()方法、insertBefore()方法
删除节点:nodeObject.removeChild(node)
替换节点:nodeObject.replaceChild(new_node, old_node)
new做了什么及实现
1、创建了一个空的js对象(即{}) ——> varobj=new Object()
2、将空对象的原型指向构造函数的原型——> obj.__proto__ = Func.prototype
3、将空对象作为构造函数的上下文(改变this指向)
4、对构造函数有返回值的判断,如果该函数没有返回对象,则返回
this
function create() {
// 1、获得构造函数,同时删除 arguments 中第一个参数
Con = [].shift.call(arguments); //等效于Array.prototype.slice.call( arguments)
// 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性
let obj = Object.create(Con.prototype);
// 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性
let ret = Con.apply(obj, arguments);
// 4、优先返回构造函数返回的对象
return ret instanceof Object ? ret : obj;
};
对第一步操作的解释:
[].slice.call(arguments等效于Array.prototype.slice.call( arguments ),当[].slice.call() 传入 arguments对象的时候,通过 call函数改变原来 slice方法的this指向, 使其指向arguments,并对arguments进行复制操作,而后返回一个新数组。至此便是完成了arguments类数组转为数组的目的!
同理,
shift()
方法删除数组第一项,并返回删除项。根据上边的理解,这句代码意思就是: “删除并拿到arguments
的第一项”
promise的原理和特性
原理:
Promise 也还是使用回调函数,只不过是把回调封装在了内部,使用上一直通过 then 方法的链式调用,使得多层的回调嵌套看起来变成了同一层的,书写上以及理解上会更直观和简洁一些。
特性:
Promise为es6标准语法,主要用于解决异步回调问题和使用第三方库的信任问题(这些三方库是否能够按照我们的设想,在正确的时间正确地调用回调?Promise 对象可分为三种状态。pending(进行中)、fulfilled(已成功)和rejected(已失败),Promise 状态为不可逆的,也就是说promise只能从pending->fulfilled,或从 pending -> rejected 由于异步操作的结果决定状态。一旦状态改变此对象将不能在复用)
原型和原型链
原型:
①所有引用类型
都有一个__proto__(隐式原型)
属性,属性值是一个普通的对象
②所有函数
都有一个prototype(原型)
属性,属性值是一个普通的对象
③所有引用类型的__proto__
属性指向
它构造函数的prototype
原型链:
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,
如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。
①一直往上层查找,直到到null还没有找到,则返回undefined
②Object.prototype.__proto__ === null
③所有从原型或更高级原型中的得到、执行的方法,其中的this在执行时,指向当前这个触发事件执行的对象
some和every的使用和区别
some和every都用于检测数组中的元素是否满足指定条件(函数提供)。
区别
some只要有一个元素满足就返回true,剩余的元素不会再执行检测,没有满足条件的元素返回false
every只要有一个元素不满足就返回false,剩余的元素不会再执行检测,如果所有元素都满足条件返回true
共同特点
不会对空数组进行检测,不会改变原始数组。
JS遍历方法forEach、map、for...in、for...of使用场景和区别?
- forEach:支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组)
- map :支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组)
- for…in:循环遍历的值都是数据结构的键值(用于遍历对象)
- for…of:它是ES6中新增加的语法,用来循环获取一对键值对中的值
一个数据结构只有部署了 Symbol.iterator 迭代器属性, 才具有 iterator接口可以使用 for of循环。
哪些数据结构部署了 Symbol.iteratoer属性了呢?
- Array
- Map
- Set
- String
- arguments对象
- Nodelist对象, 就是获取的dom列表集合
map和foreach区别:
实现map
Array.prototype.newMap = function(fn) {
var newArr = [];
for(var i = 0; i<this.length; i++){
newArr.push(fn(this[i],i,this))
}
return newArr;
}
说一下异步编程的实现方式 ?
1.回调函数
优点:简单、容易理解
缺点:不利于维护,代码耦合高
2.事件监听(采用时间驱动模式,取决于某个事件是否发生):
优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
缺点:事件驱动型,流程不够清晰
3.发布/订阅(观察者模式)
类似于事件监听,但是可以通过‘消息中心ʼ,了解现在有多少发布者,多少订阅者
4.Promise对象
优点:可以利.then方法,进行链式写法;可以书写错误时的回调函数;
缺点:编写和理解,相对较难
5.Generator函数
优点:函数体内外的数据交换、错误处理机制
缺点:流程管理不方便
6.async函数
优点:内置执⾏器、更好的语义、更好的适用性、返回的是Promise、结构清晰。
缺点:错误处理机制
如何判断数组?
- Object.prototype.toString.apply(value) === '[object Array]'
- Array.isArray(arr)
- arr instanceof Array
this
- 普通函数调用,此时 this 指向 window
- 对象方法调用, 此时 this 指向 该方法所属的对象
- 如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window
- 如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。
- 如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象
- this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的
- 赋值给变量的时候并没有执行所以最终指向的是window
- 构造函数调用, 此时 this 指向 实例对象,new关键字可以改变this的指向
- 如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。
- 还有一点就是虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊。
- 当我们使用箭头函数时,函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
JS中的this_所以this的值不能得到维持-CSDN博客
var / let / const的区别
- 声明过程(var在任何语句执行前都已经完成了声明和初始化, let变量只是先完成声明,并没有到初始化, function 声明、初始化、赋值一开始就全部完成,所以函数的变量提升优先级更高)
- 内存分配(var直接在栈内存里预分配内存空间, let、const不会分配)
- 变量提升(let只是创建过程提升,初始化过程并没有提升,var的创建和初始化过程都提升了)
使用const声明的对象属性是可以修改的,但整个对象本身是不可更改的
JS的变量提升
- js会将变量的声明提升到js顶部执行,因此对于这种语句:var a = 2;其实上js会将其分为var a;和a = 2;两部分,并且将var a这一步提升到顶部执行。
- 变量提升的本质其实是由于js引擎在编译的时候,就将所有的变量声明了,因此在执行的时候,所有的变量都已经完成声明。
- 当有多个同名变量声明的时候,函数声明会覆盖其他的声明。如果有多个函数声明,则是由最后的一个函数声明覆盖之前所有的声明。
- 函数创建有两种方式:1、函数声明(function foo ());2、函数表达式。(var foo = function ())【只有函数声明形式才有函数提升】。
- 函数提升优先级高于变量提升,且不会被同名变量声明时覆盖,但是会被变量赋值后覆盖
// 声明式 function foo () { // to do...
相当于
// 函数字面量 var foo = function () { // to do... }
console.log(bar); // f bar() { console.log(123) } console.log(bar()); // undefined var bar = 456; function bar() { console.log(123); // 123 } console.log(bar); // 456 bar = 789; console.log(bar); // 789 console.log(bar()) // bar is not a function
相当于
// js执行步骤 // 函数提升,函数提升优先级高于变量提升 var bar = function() { console.log(123) }; // 变量提升,变量提升不会覆盖(同名)函数提升,只有变量再次赋值时,才会被覆盖 var bar; console.log(bar); console.log(bar()); // 变量赋值,覆盖同名函数字面量 bar = 456; console.log(bar); // 再次赋值 bar = 789 console.log(bar); console.log(bar());
console.log(a); var a=10; console.log(b); let b=10; 因为var定义的变量有变量提升,所有第一个输出undefind 第二个let定义的变量没有变量提升,所以第二个会报错
关于const定义的值能不能修改
const obj = {
a:10
}
obj.a = 20;
console.log(obj.a) // 20
是不是觉得很奇怪,const 给人的印象就是定义一个常量,
其实这个说法不严谨,准确的说:const 定义的变量,这个变量的值可以改动,但是对于基本类型的数据来说,值就保存在变量指向的内存地址,所以不能改动(就像上面的 那种 字符串,数字,布尔值)
对于复合类型的数据(主要是对象和数组),他们的值是可以改变的
const obj = {
a:10
}
obj = {
a:20
}
console.log(obj.a) // Identifier 'obj' has already been declared
这种情况报错是因为,你改变了obj的内存地址,而不是改变的 obj对象里面的a的值了,这点要搞清楚,
其实这个和 变量的基本类型,引用类型 原理是一样的
JS的闭包
我们想在一个函数内部也有限权访问另一个函数内部的变量该怎么办呢?闭包就是用来解决这一需求的,闭包的本质就是在一个函数内部创建另一个函数。
闭包的特性:
①函数嵌套函数
②函数内部可以引用函数外部的参数和变量
③参数和变量不会被垃圾回收机制回收
闭包两种的主要形式:
①函数作为返回值
②闭包作为参数传递
好处:
①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
③匿名自执行函数可以减少内存消耗
坏处:
①其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响
向数组制定位置插入一个元素?
使用JavaScript splice()方法,array.splice(添加/删除的起始位,添加删除的个数(设置为0不会添加/删除),添加的数据(任选))
知道数据结构有哪些?
script标签中的defer和async
<script>
标签打开defer
或async
属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。defer与async的区别是:
defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;
async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。
一句话,defer是“渲染完再执行”,async是“下载完就执行”。另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本哪个先下载完成哪个就立即执行。
mixin
js的一种设计模式,Mixin模式就是一些提供能够被一个或者一组子类简单继承功能的类,意在重用其功能。通俗的讲就是把一个对象的方法和属性拷贝到另一个对象上。
链式调用原理
链式调用原理就是作用域链
实现需要做的工作:
- 对象方法的处理 (操作方法)
- 处理完成返回对象的引用(操作对象)
第2步链式实现的方式:
<1> this的作用域链
<2> 返回对象本身, 同this的区别就是显示返回链式对象;
<3> 闭包返回对象通过调用覆盖valueOf方法实现,副作用获取结果需要调用valueOf;(此种方法适用于操作方法有相互依赖的情况下使用)
querySelectorAll 和 getElementsByTagName区别
object属性或方法是有序的吗?
不一定有序,如果key是整数(如:123)或者整数类型的字符串(如:“123”),那么会按照从小到大的排序。除此之外,其它数据类型,都安装对象key的实际创建顺序排序。如果想顺序遍历一组数据,请使用数组并使用 for 语句遍历。
object的key也可以是数字,会被默认转为字符串
require和import的区别
1.遵循规范不同
require是AMD规范引入式,本质是一个赋值的过程,需要的结果赋值给某个变量
import是ES6的一个语法标准,如果要兼容浏览器,必须转化为ES5的写法就是被转成require
2.调用时间不同
require是运行时调用,所以需要可以放在代码的任何地方
import是编译时调用,所以只能放在文件头部
JS如何判断数字?
1.isNaN() isNaN将把字符串当作数字0来处理,所以检查不严谨
2.正则表达式 var reg=/^[0-9]+.?[0-9]*$/
3.利用typeof的返回值,如果返回的值为Number,则为数字
谈一谈你对原生 JavaScript了解程度 ?
数据类型、运算、对象、Function
继承、闭包、作用域、原型链、事件、RegExp
JSON、Ajax、DOM、BOM、内存泄漏、跨域
异步装载、模板引擎、前端 MVC
路由、模块化、Canvas、ECMAScript
原型链继承的缺点
- 通过原型链继承自同一个父类,现在在某个实例上修改了继承来的某一个属性,那所有的实例都会共享这个修改
- 在通过原型链实现继承时,原型实际上会成为另一个类型的实例。所以父类的实例属性实际上会成为子类的原型属性。结果就是所有的子类的实例都会共享父类的实例属性(引用类型的)。
- 在创建子类型的实例时,没有办法在不影响所有实例的情况下,向父类型的构造函数传递参数。
JSBridge
主要是给 JavaScript 提供调用 Native 功能的接口,让混合开发中的前端部分可以方便地使用 Native 的功能(例如:地址位置、摄像头)。
是 Native 和非 Native 之间的桥梁,它的核心是构建 Native 和非 Native 间消息通信的通道,而且这个通信的通道是双向的。
双向通信的通道:
- JS 向 Native 发送消息: 调用相关功能、通知 Native 当前 JS 的相关状态等。
- Native 向 JS 发送消息: 回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。
JavaScript 调用 Native的方式
主要有两种:注入API 和 拦截URL SCHEME。
防抖和节流
一、防抖
触发高频函数事件后,n秒内函数只能执行一次,如果在n秒内这个事件再次被触发的话,那么会重新计算时间
function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function() {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn();
}, 500);
};
}
- search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
- 频繁操作点赞和取消点赞,因此需要获取最后一次操作结果并发送给服务器
二、节流
高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
function throttle(fn) {
let canRun = true; // 通过闭包保存一个标记
return function () {
if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
canRun = false; // 立即设置为false
setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
fn();
// 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
canRun = true;
}, 500);
};
}
- 鼠标不断点击触发,mousedown(单位时间内只触发一次)
- window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用节流来让其在一定时间内触发一次
JS判断是否是空对象?
1.将json对象转化为json字符串,再判断该字符串是否为"{}"
var data = {};
var b = (JSON.stringify(data) == "{}");
alert(b);//true
2.for in 循环判断
var obj = {};
var b = function() {
for(var key in obj) {
return false;
}
return true;
}
alert(b());//true
3.使用ES6的Object.keys()方法,这是ES6的新方法, 返回值也是对象中属性名组成的数组
var data = {};
var arr = Object.keys(data);
alert(arr.length == 0);//true
JS如何判断对象?
var is_obj = (value)=>Object.prototype.toString.apply(value) === '[object Object]'
对 Ajax 原理的理解 与原生AJAX写法
1.Ajax 的原理简单来说是在⽤户和服务器之间加了—个中间层( AJAX 引擎),通过 XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据,然后⽤ javascript 来操作 DOM ⽽更新⻚⾯。使⽤户操作与服务器响应异步化。这其中最关键的⼀步就是从服 务器获得请求数据
2.Ajax 的过程只涉及 JavaScript 、 XMLHttpRequest 和 DOM 。 XMLHttpRequest 是 ajax的核⼼机制
优点:
1)通过异步模式,提升了⽤户体验.
2)优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占⽤.
3) Ajax 在客户端运⾏,承担了⼀部分本来由服务器承担的⼯作,减少了⼤⽤户量下的服 务器负载。
4)Ajax 可以实现动态不刷新(局部刷新)
缺点:
1)安全问题,AJAX 暴露了与服务器交互的细节。
2)对搜索引擎的⽀持⽐较弱。
3)不容易调试。
var Ajax={
get: function(url, fn) {
// XMLHttpRequest对象用于在后台与服务器交换数据
/** 1. 创建连接 **/
var xhr = new XMLHttpRequest();
/** 2. 连接服务器 **/
xhr.open('GET', url, true);
/** 3. 发送请求 **/
xhr.send();
/** 4. 接受请求 **/
xhr.onreadystatechange = function() {
// readyState == 4说明请求已完成
if (xhr.readyState == 4 && xhr.status == 200 || xhr.status == 304) {
// 从服务器获得数据
fn.call(this, xhr.responseText);
}
};
},
// data应为'a=a1&b=b1'这种字符串格式,在jq里如果data为对象会自动将对象转成这种字符串格式
post: function (url, data, fn) {
var xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
// 添加http头,发送信息至服务器时内容编码类型
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
fn.call(this, xhr.responseText);
}
};
xhr.send(data);
}
}
promise和AJAX结合写法
const ajaxPromise = param => {
return new Promise((resovle, reject) => {
var xhr = new XMLHttpRequest();
xhr.open(param.type || "get", param.url, true);
xhr.send(param.data || null);
xhr.onreadystatechange = () => {
var DONE = 4; // readyState 4 代表已向服务器发送请求
var OK = 200; // status 200 代表服务器返回成功
if(xhr.readyState === DONE){
if(xhr.status === OK){
resovle(JSON.parse(xhr.responseText));
} else{
reject(JSON.parse(xhr.responseText));
}
}
}
})
}
toString和valueOf有什么区别?
共同点:在的的JavaScript中,的的toString()方法和的的valueOf()方法,在输出对象时会自动调用。
不同点:二者并存的情况下,在数值运算中,优先调用valueOf,字符串运算中,优先调用了的的的toString。
返回值类型的差别:
1. toString一定将所有内容转为字符串
2. valueOf返回指定对象的原始值,不进行类型转换
用途的差别:
1. valueOf专用于算数计算和关系运算
2. toString专用于输出字符串
用法详细看这篇文章:HTTPS://blog.csdn.net/Web_J/article/details/84106129
ES6map、set及弱引用
Map 是 ES6 中新增的数据结构,Map 类似于对象,但普通对象的 key 必须是字符串或者数字,而 Map 的 key 可以是任何数据类型
ES6中提供了Set数据容器,这是一个能够存储无重复值的有序列表。
WeakSet 、WeakMap 只能保存引用类型存在于需要考虑生命周期防止内存泄漏的地方
Object.entries()以及是它的逆运算Object.fromEntries()
1、Object.entries()为ES8中新特性
Object.entries()获取对象的键名和键值,每一对键名和键值组成新的数组。
let obj = {
name:'蓟县',
scene:['长城','梨木台','盘山','溶洞']
}
console.log(Object.entries(obj))2、 Object.fromEntries() 为ES10中新特性
Object.fromEntries()
方法把键值对列表转换为一个对象。const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);const obj = Object.fromEntries(entries);
console.log(obj); //Object { foo: "bar", baz: 42 }
CommonJS 与 ES6 Module 的区别
语法差异:
- CommonJS模块使用
require()
函数来导入模块,使用module.exports
或exports
对象来导出模块。- ES6模块使用
import
语句来导入模块,使用export
关键字来导出模块。静态 vs. 动态:
- ES6模块是静态的,意味着导入和导出的模块必须在代码执行之前确定。这使得工具能够更好地优化代码。
- CommonJS模块是动态的,意味着导入和导出的模块可以在运行时确定。
顶层上下文:
- 在CommonJS模块中,模块的代码会在顶层上下文中执行,所以
require()
调用可以在任何地方使用。- 在ES6模块中,模块的代码在一个独立的模块作用域中执行,导入和导出语句只能出现在模块的顶层。
默认导出:
- 在CommonJS模块中,可以通过
module.exports
来导出一个默认的值,其他模块可以通过require()
来导入。- 在ES6模块中,默认导出通过
export default
语法来实现,其他模块可以通过import
语句的as
关键字来导入。循环依赖:
- CommonJS模块支持循环依赖,即模块A可以依赖模块B,而模块B又可以依赖模块A。
- ES6模块不支持循环依赖,循环依赖会导致其中一个模块被解析为未定义。
export和module.export的区别
exports = module.exports = {}
exports 是module.exports的引用,怎么理解这句话呢?
大概就是 var a = {}; var b = a; a 和 b 之间的区别吧export是设置导出模块对象的指定属性
module.export既可以设置导出模块的所有属性,又可以设置导出模块的指定属性。
require 引用模块后,返回的是 module.exports 而不是 exports
exports.xxx 相当于在导出对象上挂属性,该属性对调用模块直接可见
exports = 相当于给 exports 对象重新赋值,调用模块不能访问 exports 对象及其属性
如果此模块是一个类,就应该直接赋值 module.exports,这样调用者就是一个类构造器,可以直接 new 实例
Relec
反射,MDN的定义:Reflect是一个内建的对象,用来提供方法去拦截JavaScript的操作。Reflect不是一个函数对象,所以它是不可构造的,也就是说它不是一个构造器,你不能通过`new`操作符去新建或者将其作为一个函数去调用Reflect对象。Reflect的所有属性和方法都是静态的。
对于我个人的理解而言,Reflect设计的目的是为了优化Object的一些操作方法以及合理的返回Object操作返回的结果,对于一些命令式的Object行为,Reflect对象可以将其变为函数式的行为
typeof和instanceof区别
JS中的typeof和instanceof常用来判断一个变量是否为空,或者是什么类型。
typeof一般返回以下几个字符串:
"number", "string","boolean","object","function","undefined"
对于Array,Null等特殊对象使用typeof一律返回object,这正是typeof的局限性。
instanceof
instanceof用来检测某个对象是不是另一个对象的实例。
官方的话:用来测试一个对象在其原型链中是否存在一个构造函数prototype属性
symbol哪些场景更适合
最大特点: 独一无二
当我们只需要知道每个变量的值都是百分百不同的即可,这时候我们就可以用Symbol。
var s1 = Symbol(); var s2 = Symbol(); s1 === s2 // false // 有参数的情况 var s1 = Symbol("foo"); var s2 = Symbol("foo"); s1 === s2 // false
应用场景1:使用Symbol来作为对象属性名(key)
应用场景2:使用Symbol来替代常量
应用场景3:使用Symbol定义类的私有属性/方法
JS生成器Generator
相当于一个能够控制进展的迭代器
generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。
generator和函数不同的是,generator由
function*
定义(注意多出的*号),并且,除了return
语句,还可以用yield
返回多次。
next()
方法会执行generator的代码,然后,每次遇到yield x;
就返回一个对象{value: x, done: true/false}
,然后“暂停”。返回的
value
就是yield
的返回值,done
表示这个generator是否已经执行结束了。如果
done
为true
,则value
就是return
的返回值。当执行到
done
为true
时,这个generator对象就已经全部执行完毕,不要再继续调用next()
了。用处
JS的多态:重载和重写
重载:同一个方法,根据参数不同,实现不同效果
重写:在类的继承中,子类可以重写父类的方法
什么是面向对象?
面向对象是一种编程思想,JS本身就是基于面向对象构建出来的。(js中有很多内置类,如promise就是es6新增的一个内置类,我们可以基于new promise来创建一个实例,用来管理异步编程。)
hash和history的区别
一、实现方式不同
hash—— 即地址栏 URL 中的 # 符号,原理是
onhashchange
事件,可以在window对象上监听这个事件history—— 运用了浏览器的历史记录栈,之前有back、forward、go方法,之后在HTML5中新增了pushState()和replaceState()方法。
二、刷新页面时反应不同
hash——加载到地址栏对应的页面,# 后面的数据不会往后台传,刷新只是会请求#前的url
history——404报错(刷新是网络请求,没有后端准备时会报错)因为都是/分开的,会认为是一个完整的url,如果后台没有对应的url,会出现404
三、传参方式不一样
hash——传参是基于 url 的,如果要传递复杂的数据,会有体积的限制
history——不仅可以在url里放参数,还可以将数据存放在一个特定的对象中,
通过,当活动历史记录条目更改时,将触发popstate事件,
history.pushState()
或history.replaceState()
不会触发popstate
事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在Javascript代码中调用history.back()
或者history.forward()
方法)
JS事件循环
JavaScript 是单线程语言,有一个主线程 ,和调用栈也称之为执行栈。所有的任务都会放到调用栈中等待主线程来执行。
JavaScript 的 Event Loop 是伴随着整个源码文件生命周期的,只要当前 JavaScript 在运行中,内部的这个循环就会不断地循环下去,去寻找 队列里面能执行的 任务。
1、不管是同步还是异步,js都会按顺序执行,只是不等待异步的执行结果而已(并不是遇到异步的就绕过不执行)
2、同步的任务没有优先级之分,异步执行有优先级,先执行微任务(microtask队列),再执行宏任务(macrotask队列),同级别按顺序执行
微任务: `process.nextTick` ,`promise.then` ,`MutationObserver(监听DOM树变化)`
宏任务:`script` , `setTimeout` ,`setInterval` ,`setImmediate` ,`I/O` ,`UI rendering`
JS的数据类型有哪些
基本数据类型:Undefined、Null、Boolean、Number、String、symbol
引用数据类型:数组,函数,对象,日期,正则表达式
数组的哪些方法会改变原数组
let a = [1,2,3];
ES5:
a.splice()/
a.sort() /
a.pop()/
a.shift()/
a.push()/
a.unshift()/
a.reverse()
ES6:
a.copyWithin()(
指定位置的成员复制到其他位置) /
a.fill()(填充)
(在构造函数中)调用 super(props) 的目的是什么
在 super() 被调用之前,子类是不能使用 this 的,在 ES6 中,子类必须在 constructor 中调用 super()。传递 props 给 super() 的原因则是便于(在子类中)能在 constructor 访问 this.props。
在 constructor 中必须调用 super 方法,因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工,而 super 就代表了父类的构造函数。super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B,因此 super() 相当于 ```A.prototype.constructor.call(this, props)``。
什么是时间复杂度
时间复杂度就是用来方便开发者估算出程序的运行时间
我们该如何估计程序运行时间呢,我们通常会估计算法的操作单元数量,来代表程序消耗的时间
假设算法的问题规模为n,那么操作单元数量便用函数f(n)来表示
随着数据规模n的增大,算法执行时间的增长率和f(n)的增长率相同,这称作为算法的渐近时间复杂度,简称时间复杂度,记为 O(f(n))
preocess.nextTick和promise.then谁会先执行
preocess.nextTick优先级大于promise.then
如何实现继承?
call继承
寄生组合继承(Object.creat())
通过构造函数继承属性,通过原型链继承方法
// 父类 function Animal(name) { this.name = name; } Animal.prototype.walk = function() { console.log(this.name + ' is walking.'); }; // 子类 function Dog(name, breed) { Animal.call(this, name); // 调用父类构造函数,继承属性 this.breed = breed; } // 使用原型链继承方法 Dog.prototype = Object.create(Animal.prototype); Dog.prototype.constructor = Dog; //og.prototype = Object.create(Animal.prototype);: 这行代码通过Object.create方法创建了一个空对象,并将该空对象的原型指向Animal.prototype,这样就建立了子类Dog的原型和父类Animal原型之间的关联。这样一来,Dog实例就可以通过原型链访问到Animal原型上的方法。 Dog.prototype.constructor = Dog;: 在第一步之后,由于Dog.prototype被重写为指向Animal.prototype,因此需要手动修复constructor属性,以确保它指向正确的构造函数。如果不进行这一步,constructor属性将指向Animal构造函数,而不是Dog构造函数。 Dog.prototype.bark = function() { console.log(this.name + ' is barking.'); }; // 创建子类实例 var myDog = new Dog('Buddy', 'Labrador');
es6继承(extends super)
123 instanceof Number
1 123 instanceof Number 2 new Number(123) instanceof Number 3 Number(123) instanceof Number
instanceof这个运算符是用来测试一个对象的原型链上是否有该原型的构造函数,即instanceof左表达式要是一个对象,右侧表达式要是一个构造函数,并且左侧是右侧实例化出来的对象才会返回true
第一个 首先左侧为Number类型,并不是一个对象,更不是由Number实例化出来的(基本包装类型),所以为false
第二个 左侧使用Number构造实例化对象 右侧为Number构造 ,所以为true
第三个 左侧没有使用new所以并不是使用构造函数实例化 而是使用Number这个函数返回了一个数字, 所以为false
手写instanceof
function instanceOf(left, right) { let leftValue = left.__proto__; let rightValue = right.prototype; while(leftValue ){ if(leftValue === rightValue ){ return true }else{ leftValue = leftValue.__proto__ } } return false }