js知识点

#

js知识点汇总

## 线程和进程是什么?举例说明
> 进程:cpu分配资源的最小单位(是能拥有资源和独立运行的最小单位)
> 线程:是cpu最小的调度单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
例子:比如进程=火车,线程=车厢

> 一个进程内有多个线程,执行过程是多条线程共同完成的,线程是进程的部分。
> 一个火车可以有多个车厢
> 每个进程都有独立的代码和数据空间,程序之间切换会产生较大的开销;线程可以看作轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。

## 为什么js是单线程
> JS是单线程的原因主要和JS的用途有关,JS主要实现浏览器与用户的交互,以及操作DOM。
> 如果JS被设计为多线程,如果一个线程要修改一个DOM元素,另一个线程要删除这个DOM元素,这时浏览器就不知道该怎么办,为了避免复杂的情况产生,所以JS是单线程的。

> 为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

## js中的数据类型有哪几种?是如何存储值的? 了解包装对象吗?
>- 基本数据类型: Number、String、Boolean、null、undefined、Symbol(es6新增,表示独一无二的值,主要是为了解决可能出现的全局变量冲突)和Bigint(es10新增)
>- 引用类型:object,其他的如Array,Function,date等数据类型都可以理解为object类型的子类.
>- 原始数据类型:直接存储在栈(stack)中,占据空间小,大小固定,属于被频繁使用数据,所以放入栈中存储。属于基本数据类型
>- 引用数据类型(复杂数据类型):同时存储在栈(stack)和堆(heap)中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。如果都存放在栈中,将会影响程序运行的性能。
>- 基础数据类型临时创建的临时对象,称为包装对象。其中 number、boolean 和 string 有包装对象,代码运行的过程中会找到对应的包装对象,然后包装对象把属性和方法给了基本类型,然后包装对象被系统进行销毁。

## JS基本和非基本数据类型之间的一些区别?
>- 基本数据类型是不可变的,而非基本数据类型是可变的.
>- 基本数据类型是不可变的,因为它们一旦创建就无法更改,但非基本数据类型刚可更改,意味着一旦创建了对象,就可以更改它.
>- 将基本数据类型与其值进行比较,这意味着如果两个值具有相同的数据类型并具有相同的值,那么它们是严格相等的.
>- 非基本数据类型不与值进行比较.例如,如果两个对象具有相同的属性和值,则它们严格不相等.

## 对内存泄漏的了解
1. 理解
>- 定义:程序中已在堆中分配的内存,因为某种原因未释放或者无法释放的问题
>- 简单理解: 无用的内存还在占用,得不到释放和归还,比较严重的时候,无用的内存还会增加,从而导致整个系统卡顿,甚至崩溃。
2. 生命周期
– 分配期
> 分配所需要的内存,在js中,是自动分配的
– 使用期
> 使用分配的内存,就是读写变量或者对象的属性值
– 释放期
> 不需要时将该内存释放,js会自动释放(除了闭包和一些bug以外)
内存泄漏就是出现在这个时期,内存没有被释放导致的
3. 可能出现内存泄漏的原因
> 1. 意外的全局变量
>“`
> //在js中,当引用一个未声明的变量的时候,会在全局对象上创建一个新的变量,在浏览器中全局对象是window。
> function foo(arg) {
> bar = “some text”; //等同于 window.bar = “some text”;
> this.foo = “some text”; //等同于 window.foo = “some text”;
> }
>“`
> 2. DOM元素清空时,还存在引用
> 3. 不合理的闭包
> 4. 遗忘的定时器或回调函数,以及循环函数对外部变量的引用

– **如何优化内存泄漏?**
>- 全局变量先声明再使用
>- 避免过多使用闭包
>- 注意清除定时器和事件监听器

## js中数组合并的方法
– js数组合并
“`
let arr1 = [‘温情’, ‘刘聪’]
let arr2 = [‘杨和苏’, ‘邓紫棋’]
let arr3 = [‘周延’]
“`
1. arr1.concat(arr2, ······)
> es5 Array.concat() 合并两个数组, 返回新数组,不会改变原数组
arr = arr1.concat(arr2, arr3);
console.log(arr); // [“温情”, “刘聪”, “杨和苏”, “邓紫棋”, “周延”]

2. […arr1, …arr2,······]
> es6 展开运算符(…)
arr = […arr1, …arr2, …arr3];
console.log(arr); // [“温情”, “刘聪”, “杨和苏”, “邓紫棋”, “周延”]

3. push(…arr)
> push 结合 …[] 来实现, 会更改原数组
arr1.push(…arr2, …arr3)
console.log(arr1); // [“温情”, “刘聪”, “杨和苏”, “邓紫棋”, “周延”]

– 适合两个数组,不适合多个数组的方法
1. for + push
“`
for(let i in arr2) {
arr1.push(arr2[i])
}
console.log(arr1); // [“温情”, “刘聪”, “杨和苏”, “邓紫棋”]
“`
2. arr1.push.apply(arr1, arr2)
“`
arr1.push.apply(arr1, arr2)
console.log(arr1); // [“温情”, “刘聪”, “杨和苏”, “邓紫棋”]
“`

## 合并对象的方法
– Object.assign()
> es6 Object.assign()方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)
Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象。
“`
let obj1 = {name: ‘温情’}
let obj2 = {age: ’22’}
const newObj = Object.assign({}, obj1, obj2);
console.log(newObj); // {name: “温情”, age: “22”}
“`
>- 注意! Object.assign()实行的是浅拷贝,也就是说如果源对象的属性是一个对象,那么目标对象得到的是这个对象的引用
>“`
> let obj1 = {name: {chinese: ‘杨和苏’, english: ‘keyNG’}}
> const newObj = Object.assign({}, obj1);
> console.log(newObj); // name: {chinese: “杨和苏”, english: “keyNG”}
> obj1.name.english = ‘pig’;
> console.log(newObj); // name: {chinese: “杨和苏”, english: “pig”}
>“`

## 什么是作用域,什么是作用域链?
>- 规定变量和函数的可使用范围称为作用域
>- 查找变量或者函数时,需要从局部作用域到全局作用域依次查找,这些作用域的集合称作用域链。如果这个变量在自己的作用域中没有,那么它会寻找父级的,直到最顶层。
>- 变量取值到创建这个变量的函数的作用域中取值.但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链.
>- JS中的作用域链主要用于解析变量的值.如果没有这个,在不同的作用域内定义了许多变量,JS很难为变量选择某个值.
>- 作用域链会被保存到一个隐式属性[[scope]]中去,这个属性是我们用户访问不到的,但的的确确是存在的是让js引擎来访问的里面存储的就是作用域链
>- 作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和函数。

## JS如何实现异步编程(5种)
1. 回调函数(callback)
>- 回调函数是指:一个函数A作为参数(函数引用)传递到另一个函数B中,函数B执行调用函数A。此时函数A叫做回调函数。函数B作为参数的函数为高阶函数
>- 优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。),容易理解和部署
>- 缺点:回调地狱,每个任务只能指定一个回调函数,不能 return.不利于代码的阅读,和维护,各部分之间高度耦合,流程会很混乱.

– 联系闭包函数,只有闭包才可以突破作用域链,将函数内部的变量和方法传递到外部,让外部函数可以访问到内部函数的变量和方法。因此回调函数也是闭包函数

2. 事件监听
>- 这种思路是说异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。比如一个我们注册一个按钮的点击事件或者注册一个自定义事件,然后通过点击或者trigger的方式触发这个事件。

3. Promise(列表里有)
4. Generator
>- 在function关键字后加一个*,那么这个函数就称之为generator函数
>- 函数体有关键字yield,后面跟每一个任务,也可以有return关键字,保留一个数据
>- 通过next函数调用,几个调用,就是几个人任务执行

示例代码
“`
function* showNums(){
yield ‘one’;
yield ‘two’;
return ‘three’;
}

let alertNum = showNums();

alertNum.next() //{done:false,value:”one”}
alertNum.next() //{done:false,value:”two”}
alertNum.next() //{done:true,value:”three”}
alertNum.next() //{value:undefined,done:true}

//定义了一个showNums的生成器函数,调用之后返回了一个迭代器对象(alertNum)
//调用next方法,函数会依次执行yield语句(每次返回一条数据),输出当前的状态done(迭代器是否遍历完成)以及相应yield返回的数据

/*生成器调用一次next,则执行一次yield语句,并在该处暂停,但是当return完成之后,就退出了生成器函数,
即使后续还有yield操作也不再执行了*/
“`
特殊例子
“`
function* showNums(){
yield ‘one’;
yield* showNumbers();
return ‘three’;
}

function* showNumbers(){
yield 1+1;
yield 3;
}

var show = showNums();
show.next() // {done: false, value: “one”}
show.next() // {done: false, value: 2}
show.next() // {done: false, value: 3}
show.next() // {done: true, value: “three”}

/*可以看到我们在生成器showNums中还调用了一个showNumbers生成器函数,并且在yield后面也带了一个*号,
这是必须的,如果不带*号将无法调用showNumbers生成器函数,不能按我们想要的输出showNumbers函数内的内容,
看得出来生成器函数相较于回调函数操作本身不仅能获取到当前具体执行到的步骤和返回值,还能获取到调用的子函数*/

//注意:yield和yield*只能在generator函数内部使用,一般的函数内使用会报错
“`

5. async/await(ES7 列表里有)

## js中的堆内存与栈内存
> 在js引擎中对变量的存储主要有两种位置,堆内存和栈内存。

> 栈(stack):由编译器自动分配释放,存放函数的参数值,局部变量等;
> 堆(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统释放(垃圾回收机制回收)。

> 在数据结构中,栈中数据的存取方式为先进后出。而堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。完全二叉树是堆的一种实现方式。

> 和java中对内存的处理类似,栈内存主要用于存储各种基本类型的变量,包括Boolean、Number、String、Undefined、Null以及对象变量的指针,这时候栈内存给人的感觉就像一个线性排列的空间,每个小单元大小基本相等。

> 堆内存主要负责像对象Object这种变量类型的存储

> 栈内存中的变量一般都是已知大小或者有范围上限的,算作一种简单存储。而堆内存存储的对象类型数据对于大小这方面,一般都是未知的。个人认为,这也是为什么null作为一个object类型的变量却存储在栈内存中的原因。

> 因此当我们定义一个const对象的时候,我们说的常量其实是指针,就是const对象对应的堆内存指向是不变的,但是堆内存中的数据本身的大小或者属性是可变的。
而对于const定义的基础变量而言,这个值就相当于const对象的指针,是不可变。

> 每次使用const或者let去初始化一个变量的时候,会首先遍历当前的内存栈,看看有没有重名变量,有的话就返回错误

> 使用new关键字初始化的之后是不存储在栈内存中的,new根据构造函数生成新实例,生成的是对象,而不是基本类型。
>“`
> var a = new String(“123”)
> var b = String(“123”)
> var c = “123”
> console.log(a==b,a===b,b==c,b===c,a==c,a===c)
> // true false true true true false
> console.log(typeof a)
> // “object”
>“`

> 我们可以看到new一个String,出来的是对象,而直接字面量赋值和工厂模式出来的都是字符串。但是根据我们上面的分析大小相对固定可预期的即便是对象也可以存储在栈内存的,比如null,为啥这个不是呢?再继续看
>“`
> var a = new String(“123”)
> var b = new String(“123”)
> console.log(a==b,a===b)
> // false false
>“`

> 很明显,如果a,b是存储在栈内存中的话,两者应该是明显相等的,就像null === null是true一样,但结果两者并不相等,说明两者都是存储在堆内存中的,指针指向不一致。

## 判断数据类型
>- 用typeof去判断,typeof只能判断基本数据类型,对于引用数据类型,一律返回object,在js中,数组是一种特殊的对象类型, 因此typeof一个数组,返回的是object。
>- 通过instanceof来判断,它不能检测基本数据类型,它是用来判断某个实例是否属于某种类型,是通过原型链一层层网上找, 使用它的方式可以用A instanceof B,如果A是B的实例,则返回true,否则返回flase。
>- 用constructor来判断,除了undefined和null之外,其它类型都可以通过constructor来判断,如果声明了一个构造函数,并且把它的原型指向改变了,这种情况下,constructor也不能准确的判断。
>- 通过0bject.prototype.toString,判断一个对象只属于某种内置类型,但是不能准确的判断一个实例是否属于某种类型。
原因是==因为实例对象可能会自定义toString方法,把这个方法给覆盖掉,我们可以通过函数.call()方法,可以在任意值上调用这个方法,帮助我们判断这个值的类型。==

– 封装返回数据类型的方法:
“`
function getType(value) {
if(typeof value !== ‘object’) {
return typeof value;
} else {
return Object.prototype.toString.call(value).split(‘ ‘)[1].slice(0, -1);
}
};
“`

## 什么是浏览器的同源政策?
> 我对浏览器的同源政策的理解是,一个域下的js脚本在未经允许的情况下,不能够访问另一个域的内容。这里的同源的指的是两个域的协议、域名、端口号必须相同,否则则不属于同一个域。

> 同源政策主要限制了三个方面
– 第一个是当前域下的js脚本不能够访问其他域下的cookie、localStorage和indexDB。
– 第二个是当前域下的js脚本不能够操作访问操作其他域下的DOM。
– 第三个是当前域下ajax无法发送跨域请求。

> 同源政策的目的主要是为了保证用户的信息安全,它只是对js脚本的一种限制,并不是对浏览器的限制,对于一般的img、或者script脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进行可能出现安全问题的操作。

## 跨域解决办法
1. JSONP
==定义:全称json with padding(填充式json)==
>- 在页面上,js脚本,css样式文件,图片这三种资源是可以与页面本身不同源的。jsonp就利用了script标签进行跨域取得数据。
>- JSONP允许用户传递一个callback参数给服务器端即在请求的URL后指定一个回调函数,然后服务器端返回数据时会将这个callback参数作为函数名来包裹住JSON数据。这样客户端就可以随意定制自己的函数来自动处理返回的数据了,因为请求的是脚本文件,所以会直接执行。
>- JSONP只能解决get请求,不能解决post请求。在调用失败的时候不会返回各种HTTP状态码。存在提供jsonp的服务页面有漏洞,即它返回的javascript的内容容易被人控制
“`


“`
– 使用ajax实现跨域:
“`

$.ajax({
url:’http://localhost:80/?callback=callback’,
method:’get’,
dataType:’jsonp’, //=> 执行jsonp请求
success:(res) => {
console.log(res);
}
})
function callback(data){
console.log(data);
}
“`
2. CORS跨域资源共享:
==定义:Cross-origin-resource-sharing(跨域资源共享)==
>- 需要浏览器和服务器同时支持,目前所有浏览器都支持该功能
>- 依附于AJAX,通过添加HTTP Header部分字段请求获取有权限访问的资源。
>- 浏览器将 CORS 请求分成两类:简单请求和非简单请求。
>- 对于简单请求,浏览器直接发出CORS请求。具体来说,就是会在头信息之中,增加一个Origin字段。Origin字段用来说明本次请求来自哪个源。服务器根据这个值,决定是否同意这次请求。对于如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,ajax不会收到响应信息。如果成功的话会包含一些以Access-Control-开头的字段。
>- 非简单请求,浏览器会先发出一次预检请求,来判断该域名是否在服务器的白名单中,如果收到肯定回复后才会发起请求。

> 浏览器会自动进行CORS通信,实现CORS通信的关键是后端。服务端设置Access-Control-Allow-Origin就可以开启CORS。该属性表示哪些域名跨域访问资源。

> 主要设置以下几个属性:
>- Access-Control-Allow-Origin //允许跨域的域名
>- Access-Control-Allow-Headers //允许的header类型
>- Access-Control-Allow-Methods //跨域允许的请求方式(GET,POST,PUT,DELETE,OPTIONS)

> CORS 并不是为了解决服务端安全问题,而是为了解决如何跨域调用资源。

3. Nginx反向代理
>- 通过nginx配置一个代理服务器将客户机请求转发给内部网络上的目标服务器;
并将服务器上返回的结果返回给客户端。

4. webpack(在vue.config.js文件中)中配置webpack-dev-server
“`
devServer: {
proxy: {
‘/api’: {
target: “http://39.98.123.211”, //跨域目标域名
changeOrigin: true, //是否跨域
},
},
},
“`

5. document.domain方法
> 将document.domain设置为主域名,来实现相同子域名的跨域操作,这个时候主域名下的cookie就能够被子域名所访问。同时如果文档中含有主域名相同,子域名不同的iframe 的话,我们也可以对这个iframe进行操作。这种方法只能解决主域相同的跨域问题。

6. window.name方法
> window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
7. window.postMessage() ==html5新增==
> 我们可以实现多窗口间的信息传递,通过获取到指定窗口的引用,然后调用postMessage 来发送信息,在窗口中我们通过对message信息的监听来接收信息,以此来实现不同源间的信息交换。
> otherWindow.postMessage(message, targetOrigin, [transfer]);
>- otherWindow:
其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行 window.open 返回的窗口对象、或者是命名过或数值索引的 window.frames。
>- message:
将要发送到其他 window的数据。
>- targetOrigin:
指定哪些窗口能接收到消息事件,其值可以是 *(表示无限制)或者一个 URI。
>- transfer:
可选,是一串和 message 同时传递的 Transferable 对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
8. nodejs中间件代理跨域((用的最多)通过中间件进行转发到非同源地址,请求的还是同源地址)
9. WebSocket协议跨域,这个协议没有同源限制
10. 使用location.hash的方法,我们可以在主页面动态的修改iframe窗口的hash值,然后在iframe窗口里实现监听函数来实现这样一个单向的通信。因为在iframe是没有办法访问到不同源的父级窗口的,所以我们不能直接修改父级窗口的hash值来实现通信,我们可以在iframe中再加入一个iframe,这个iframe的内容是和父级页面同源的,所以我们可以window.parent.parent来修改最顶级页面的src,以此来实现双向通信。

## 怎么让对象的一个属性不可被改变
1. Object.defineProperty()
>- 可以使用Object.defineProperty()方法,让对象的某一个属性不可变,把对象某一属性的writable和configurable设置为false.
“`
let obj = {a:1,b:2};
Object.defineProperty(obj,’c’,{
value:100000,
writable:false, //当该属性的 writable 键值为 true 时,属性的值才能被赋值操作修改
configurable:false, //当为true时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
})
obj.c = 282031283
console.log(obj.c)//100000
“`
2. object.preventExtensions()
>- 让对象不能添加新属性,可以使用object.preventExtensions()方法。(但是可以修改属性值)
“`
let obj = {a:1,b:2};
Object.preventExtensions(obj);
obj.c = 1000;
console.log(obj) //{a:1,b:2}
“`

## 判断一个函数是普通函数还是构造函数
>- 构造函数中this指向new创建的实例。所以可通过在函数内部判断this是否为当前函数的实例进而判断当前函数是否作为构造函数。
“`
function A(){
if(this instanceof A){
console.log(‘我是构造函数’)
}else{
console.log(‘我是普通函数’)
}
}
A();
new A();
“`
## JavaScript 中的提升是什么
> 提升意味着所有的声明都被移动到作用域的顶部。这发生在代码运行之前。

>对于函数,这意味着你可以从作用域中的任何位置调用它们,甚至在它们被定义之前。

> **函数声明**
>- 具有特定参数的函数称为函数声明,在JS中创建变量称为声明.
>“`
> hello(); // Prints “Hello world! ” 函数提升
> function hello(){
> console.log(“Hello world! “);
> }
>“`

> **函数表达式**
>- 当使用表达式创建函数时,称为函数表达式。如:
>“`
>notHoisted(); // TypeError: notHoisted is not a function
>var notHoisted = function() {
> console.log(‘bar’);
>};
>“`

> 对于变量,提升有点不同。它在作用域的顶部将 undefined 分配给它们。
>“`
>console.log(dog);//undefined
>var dog = “Spot”;
>“`
>- ==用var声明一个函数或变量,无论你在哪里声明它,它总是被移动到作用域的顶部==。

## js的数据类型的转换都有哪些
– 显式类型转换
– Number 函数
– 数字:转换后还是数字
– 字符串:如果可以被解析为数值,则为相应的数值,如果不能,则是 NaN,如果是空字符串那就是0
– 布尔值:true为1,false为0
– undefined:NaN
– null:0
– object:先执行valueOf,看是否能转换为相应的基本类型值,如果不可以再执行toString,看是否可以转换,如果不可以报错
– String 函数
– 数字:转换成对应的字符串
– 字符串:还是对应的字符串
– 布尔值:true为’true’,false为’false’
– undefined:undefined
– null:null
– object:先执行toString,看是否能转换,如果不可以再执行valueOf,看是否可以转换,如果不可以报错
– Boolean
– 下面这几个是false,其他都是true
– NaN
– null
– undefined
– 0
– “”
– false
– 隐式类型转换
– 三元运算条件判断表达式
– if for while 逻辑运算符(&&)判断语句

> 注意:String函数,Symbol类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。
Number函数,Symbol类型的值不能转换为数字,会报错。

## 介绍 js 有哪些内置对象
>- js中的内置对象主要指的是在程序执行前存在全局作用域里的由js定义的一些全局值属性、函数和用来实例化其他对象的构造函数对象。
>- 一般我们经常用到的如全局变量值NaN、undefined,全局函数如parseInt()、parseFloat()
>- 用来实例化对象的构造函数如Date、Object等,还有提供数学计算的单体内置对象如Math对象.

## javascript 创建对象的几种方式?
>- 我们一般使用字面量的形式直接创建对象,但是这种创建方式对于创建大量相似对象的时候,会产生大量的重复代码。但js和一般的面向对象的语言不同,在ES6之前它没有类的概念。但是我们可以使用函数来进行模拟,从而产生出可复用的对象创建方式,我了解到的方式有这么几种:

>- 第一种是工厂模式,工厂模式的主要工作原理是用函数来封装创建对象的细节,从而通过调用函数来达到复用的目的.
>“`
> function creatPerson(name, age) {
> var obj = new Object();
> obj.name = name;
> obj.age = age;
> obj.sayName = function() {
> window.alert(this.name);
> };
> return obj;
>}
>“`
> 但是它有一个很大的问题就是创建出来的对象无法和某个类型联系起来,它只是简单的封装了复用代码,而没有建立起对象和类型间的关系.
>- 第二种是构造函数模式。js中每一个函数都可以作为构造函数,只要一个函数是通过new来调用的,那么我们就可以把它称为构造函数,执行构造函数首先会创建一个对象,然后将对象的原型指向构造函数的prototype属性,然后将执行上下文中的this指向这个对象,最后再执行整个函数,如果返回值不是对象,则返回新建的对象。因为this的值指向了新建的对象,因此我们可以使用this给对象赋值,
>“`
> function Person(name, age) {
> this.name = name;
> this.age = age;
> this.sayName = function() {
> window.alert(this.name);
> };
>}
>“`
>构造函数模式相对于工厂模式的优点是,==所创建的对象和构造函数建立起了联系==,因此我们可以通过原型来识别对象的类型。
但是构造函数存在一个缺点就是==造成了不必要的函数对象的创建==,因为在js中函数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次我们都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。
>- 第三种模式是原型模式,因为每一个函数都有一个prototype属性,这个属性是一个对象,它包含了通过构造函数创建的所有实例都能共享的属性和方法.因此我们可以使用原型对象来添加公用属性和方法,从而实现代码的复用。这种方式相对于构造函数模式来说,解决了函数对象的复用问题
>“`
> function Person() {}
> Person.prototype = {
> constructor : Person,
> name : “Ning”,
> age : “23”,
> sayName : function() {
> window.alert(this.name);
> }
>};
>“`
>但是这种模式也存在一些问题,==一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如Array这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。==
>- 第四种模式是组合使用构造函数模式和原型模式,这是创建自定义类型的最常见方式。因为构造函数模式和原型模式分开使用都存在一些问题,因此我们可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。这种方法很好的解决了两种模式单独使用时的缺点,
>“`
> function Person(name, age) {
> this.name = name;
> this.age = age;
> }
> Person.prototype = {
> constructor : Person,
> sayName : function() {
> window.alert(this.name);
> }
> };
>“`
> ==但是有一点不足的就是,因为使用了两种不同的模式,所以对于代码的封装性不够好。==
>- 第五种模式是动态原型模式,这一种模式将原型方法赋值的创建过程移动到了构造函教的内部,通过对属性是否存在的判断,可以实现仅在第一次调用函数时对原型对象赋值一次的效果。
==这一种万式很好地对上面的混合模式进行了封装。==
>- 第六种模式是寄生构造函数模式,这一种模式和工厂模式的实现基本相同,我对这个模式的理解是
它主要是基于一个已有的类型,在实例化时对实例化的对象进行扩展。这样既不用修改原来的构造函数也达到了扩展对象的目的。
==它的一个缺点和工厂模式一样,无法实现对象的识别==

## js获取原型的方法?
>- p.proto
>- p.constructor.prototype
>- Object.getPrototypeOf

## 什么是闭包,为什么要用它?
> 说白了就是函数嵌套函数,内部函数能够访问外部函数的变量,且在外部被执行,就产生了闭包。常见的构造方法,是在一个函数内部定义另外一个函数。内部函数可以引用外层的变量,把该内部函数通过外部函数return到外部去执行,就形成了闭包。外层变量不会被垃圾回收机制回收。

>- 闭包的原理是作用域链,所以闭包访问的上级作用域中的变量是个对象,其值为其运算结束后的最后一个值。

>- 优点:
>- 第一个用途:创建私有变量,避免全局变量的污染
>- 第二个用途:可以使已经运行结束的函数中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收

>- 缺点:长期占用内存,容易造成内存泄漏。

>“`
> // 闭包实例
> function makeFunc() {
> var name = “Mozilla”;
> function displayName() {
> console.log(name);
> }
> return displayName;
> }
> var myFunc = makeFunc();
> myFunc(); //输出Mozilla
>“`

> 应用场景:
>- 防抖(清除旧定时器,开始新的定时器)、节流(上一个定时器没有结束,则return 不执行新的定时器)
>- 函数柯里化

## 三种事件模型是什么?
> 事件是用户操作网页时发生的交互动作或者网页本身的一些操作,现代浏览器一共有三种事件模型

>==DOMO级模型==:这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过js属性来指定监听函数。这种方式是所有浏览器都兼容的

>==IE事件模型==:在该事件模型中,一次事件共有两个过程,事件处理阶段,和事件冒泡阶段,事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过attachEvent来添加监听函数,可以添加多个监听函数,会按顺序依次执行。

>==DOM2级事件模型==:在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从document一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和IE事件模型的两个阶段相同。这种事件模型,事件绑定的函数是addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。

> ==阻止冒泡==:e.stopPropagation()方法
> ==阻止默认事件==:e.preventDefault()方法

## 简述javascript中this的指向?
> 第一准则是:==this永远指向函数运行时所在的对象,而不是函数被创建时所在的对象==
* 1.任何情况下直接在script中写入的this都是window。
* 2.函数中的this非严格模式:this指向window,严格模式时:this指向undefined。
* 3.箭头函数的this
this都指向箭头函数外上下文环境的this指向
* 4.对象中this
对象属性的this指向对象外上下文环境的this
对象方法(普通函数)中的this,指向当前对象(谁执行该方法,this就指向谁)
* 5.回调函数的this指向
* 1)、 setTimeout,setInterval回调函数不管是否是严格模式都会指向window。
* 2)、通过在函数内执行当前回调函数非严格模式:this指向window,严格模式时:this指向undefined。
* 3)递归函数中的this 非严格模式:this指向window,严格模式时:this指向undefined。
* 4)使用arguments[0]()执行函数时 this指向arguments。
* 5)事件中的回调函数,this指向事件侦听的对象(e.currentTarget);
* 6、call,apply,bind方法执行时this的指向
* 如果call,apply,bind传参时,第一个参数传入的不是null或者undefined,传入什么this指向什么
* 如果第一个参数传入的是null或者undefined ,非严格模式下指向window
* 7、在ES6的类中this的指向
* 构造函数中的this指向实例当前类所产生的新的实例对象
* 类中实例化方法中this指向谁执行该方法,this指向谁
* 类中静态方法中this执行该类或者该类的构造函数
* 类中实例化箭头方法,this仍然指向当前类实例化的实例对象
* 8、ES5的原型对象中this的指向
* 在原型的方法中,this指向实例化当前构造函数的实例化对象(谁执行该方法,this指向谁);
* 三种改变this指向的方式
* 函数名.call(this,….)this写谁就指谁。
* 函数名.apply(this,[参数1,参数2,…]) this写谁就指谁。
* 函数名. bind (this,1,2,3) this写谁就指谁。

## 改变this的指向
>- 使用ES6的箭头函数
>- 在函数内部使用_this = this
>- 使用apply、call、bind
>- new实例化一个对象

## 改变匿名函数this的指向
>- 让匿名函数中的this指向对象的两种方法
– 可以使用对象冒充强制改变this的指向
– 将this赋值给一个变量,闭包访问这个变量

## 解释一下原型和原型链?
> 原型就是一个属性,这个属性是构造函数的属性,构造函数是用来制造对象的,是构造函数制造出来的公共祖先,后面所有的对象都会继承原型的属性与方法(原型也是个对象)

> 原型:js中一切皆对象,对象都有一个隐式的属性_proto_,它指向构造函数的显式原型(prototype)

> 构造函数的prototype指向原型对象,原型对象有一个constructor属性指回构造函数,每个构造函数生成的实例对象都有一个__proto__属性,这个属性也指向原型对象。

> ES5 中新增了一个Object.getPrototypeOf()方法,我们可以通过这个方法来获取对象的原型。

> 每一个函数有一个prototype的属性,当他作为构造函数的时候,它实例化出来的函数会有一个_proto_的属性,当访问一个对象的某个属性或者方法的时候,会先在这个对象自身查找,如果没有找到,则会去它的_proto_隐式原型上查找,也就是它构造函数的prototype,如果还没有找到就会再在构造函数的prototype的_proto_中查找,直到找到或者返回undefined,这个链式查找的过程,我们称为原型链。原型链的尽头一般来说都是Object.prototype所以这就是我们新建的对象为什么能够使用toString()等方法的原因。

## 深拷贝、浅拷贝、以及如何实现?
>- 浅拷贝和深拷贝都是创建出一个新的对象.
>- 浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象,对基本数据类型来说拷贝的是值,但对引用数据类型来说拷贝的只是内存地址,深拷贝是将一个对象完整的独立拷贝一份出来,然后在堆内存中开辟一块新的内存块存储,所以不会互相影响
>- 浅拷贝:使用object.assign只拷贝地址,它是将数据中的所有数据引用下来,指向同一个存放地址,拷贝后的数据修改后,会影响到原数据中的对象数据。展开运算法也是浅拷贝(…)

“`
// 实现一个浅拷贝
function shallowCopy(obj){
if(typeof obj !== ‘object’) return
let newObj = obj instanceof Array ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj
}
“`

>- 深拷贝:JSON.parse(JSON.Stringify(…)),递归拷贝每一层对象是内容拷贝,将数据中的所有数据都拷贝下来,对拷贝后的数据进行修改,不会影响到原数据.但是会丢失函数、会把时间对象变成字符串、会把正则对象变成空对象。ES6的扩展运算符(…)仅第一层是深拷贝
>- 在深拷贝中,原始对象不与新对象共享相同的属性,而在浅拷贝中,它们具有相同的属性.
> 递归:递归就是一个函数调用其本身,通过栈来实现.每执行一个函数,就新建一个函数栈.

>“`
> //深拷贝
> function deepClone(obj) {
> var objClone = Array.isArray(obj) ? [] : {};
> if (obj && typeof obj === “object”) {
> for (key in obj) {
> if (obj.hasOwnProperty(key)) {
> if (obj[key] && typeof obj[key] === “object”) {
> objClone[key] = deepClone(obj[key]);
> } else {
> objClone[key] = obj[key];
> }
> }
> }
> }
> return objClone;
> }
>“`

## DOM事件流和事件委托?
1. 捕获阶段
2. 目标阶段
3. 冒泡阶段
>- 捕获阶段:捕获阶段不会响应任何事件,捕获事件流从根节点开始执⾏。⼀直往⼦节点查找执⾏,直到查找执⾏到⽬标节点;
>- 目标阶段:目标阶段就是指事件响应到触发事件的最底层元素上;
>- 冒泡阶段:冒泡阶段就是事件的触发响应会从最底层目标一层层地向外到最外层(根节点),事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层,途中如果有节点绑定了相应的事件处理函数,这些函数都会被一次触发。

>- 事件流描述的是从页面中接受事件的顺序,IE和网景推出了两个正好相反的概念,IE推出的是冒泡流,从下到上,网景则是事件捕获流,从上到下。

>- 首先通过addEventListener方法给元素添加点击事件,前两个参数分别是点击事件的名称和执行的回调,第三个参数就是是否开启捕获,确定事件发生的阶段,默认是false,也就是冒泡流。

>- 事件委托,一般来说,会把一个或一组元素的事件委托到它的父元素上或者更外层元素上,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。
==优点==使用事件代理我们可以不必要为每一个子元素都绑定一个监听事件,这样减少了内存上的消耗,减少了dom操作。并且使用事件代理我们还可以实现事件的动态绑定,比如说新增了一个子节点,我们并不需要单独地为它添加一个监听事件,它所发生的事件会交给父元素中的监听函数来处理。

## ajax是什么?以及如何去创建它?
– ==ajax==(AsynchronousJavascript+XML)就是用来实现客户端与服务器端的异步通信效果,实现页面的局部刷新,标准浏览器是通过XMLHttpRequest,IE浏览器则是通过ActiveXObject来实现异步通信的效果。
> 创建Ajax:
>1. 创建XMLHttpRequest对象,也就是创建一个异步调用对象
>“`
> var xhr = new XMLHttpRequest();
>“`
>2. 创建一个新的HTTP请求,并指定该请求的方法、URL及是否异步,用户名和密码
>“`
> xhr.open(‘get’,’example.php’,false,name,password);
>“`
>3. 发送请求
>- xhr.send();
>4. 接受响应
>“`
> xhr.onreadystatechange=function(){}
>“`
> – (1)当readystate值从一个值变为另一个值时,都会触发readystatechange事件,可以用addeventlistener。
> – (2)当readystate==4时,表示已经接收到全部响应数据。
> – (3)当status ==200时,表示服务器成功返回页面和数据。
> – (4)如果(2)和(3)内容同时满足,则可以通过xhr.responseText,获得服务器返回的内容。
>5. 使用JS实现DOM的局部刷新

> ==注意==:
>- 页面初次加载时,尽量在web服务器一次性输出所有相关的数据,只在页面加载完成之后,用户进行操作时采用ajax进行交互。
>- 同步ajax在IE上会产生页面假死的问题。所以建议采用异步ajax。
>- 尽量减少ajax请求次数
>- ajax安全问题,对于敏感数据在服务器端处理,避免在客户端处理过滤。对于关键业务逻辑代码也必须放在服务器端处理。
>- 我们可以通过setRequestHeader方法来为请求添加头信息。

– 如何中断ajax请求
>- 原生ajax可以通过XMLHttpRequest对象上的abort方法中断ajax请求;
应用场景: 文件上传

## ajax优点和缺点
– 优点
>- 实现页面局部刷新,在不刷新整个页面的情况下与服务器通信
>- 分担一部分后端的工作,减少服务器压力

– 缺点
>- 对seo的支持比较弱
>- ajax不支持浏览器的返回按钮

## 防抖和节流
“`
//防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
//使用场景:典型的案例就是输入框搜索:输入结束后n秒才进行搜索请求,n秒内又输入的内容,则重新计时。
//节流:高频事件触发,但在n秒内只会执行一次,如果这个时间内触发多次,只有一次生效。所以节流会稀释函数的执行频率。也可以是轮询的稀释,不需要在请求成功后立马再次发出请求。可以使用在scroll函数的事件监听上,通过事件节流来降低事件调用的频率
//防抖
function debounce(fn, delay) {
let timer = null;
return function () {
// 函数内部能访问外面的变量timer,所以是一个闭包
if(timer) {
// timer有值说明有任务正在运行,清除掉执行新的任务
clearTimeout(timer);
}
timer = setTimeout(() => {
// 调用fn回调函数,使用call改变函数this的指向
fn.call(this);
// delay定义超过多少时间不调用才执行事件
}, delay)
}
}

//节流
function throttle(fn, delay) {
let flag = true;
return () => {
if (flag) {
setTimeout(() => {
fn();
//执行完逻辑把flag还原为true,下次就可以再次执行了
flag = true;
}, delay)
}
//定时器里面的函数没执行完成,flag一直为false,一直不会再次执行定时器里面的代码,只有等到函数事件执行完成之后,把flag设置为true才可以执行下一次事件
flag = false;
}
}
“`
>- 作用:函数节流(throttle)与函数防抖(debounce)都是可以限制函数的执行频次,根据不同的场景来对执行频率进行限制,避免了函数触发频率过高导致的响应速度跟不上触发频率,出现延迟,假死或卡顿的现象。

## 同步和异步的区别,分别列举一个同步和异步的例子?
> 同步指的是当一个进程在执行某个请求的时候,如果这个请求需要等待一段时间才能返回,那么这个进程会一直等待下去,直到消息返回为止再继续向下执行。

> 异步指的是当一个进程在执行某个请求的时候,如果这个请求需要等待一段时间才能返回,这个时候进程会继续往下执行,不会阻塞等待消息的返回,当消息返回时系统再通知进程进行处理。

> alert是同步 setTimeout是异步

> 前端使用异步的场景
> 1. 定时任务:setTimeout,setInterval
> 2. 网络请求:ajax请求,动态img加载
> 3. 事件绑定

## 全局函数eval()有什么作用?
> eval()只有一个参数,如果传入的参数不是字符串,它直接返回这个参数。如果参数是字符串,它会把字符串当成javascript代码进行编译。如果编译失败则抛出一个语法错误( syntaxError )异常。如果编译成功,则开始执行这段代码,并返回字符串中的最后一个表达式或语句的值,如果最后一个表达式或语句没有值,则最终返回undefined。如果字符串抛出一个异常,这个异常将把该调用传递给eval()。缺点:不安全,非常耗性能

## 原生对象和宿主对象?
>- 原生对象是ECMAScript规定的对象,所有内置对象都是原生对象,比如Array、Date、RegExp等,它们也被称为全局对象,因为如果使用JS,内置对象不受运行环境影响;
>- 宿主对象是宿主环境比如浏览器等规定的对象,用于完善是ECMAScript的执行环境,比如Document、Location,Navigator等。这意味着它们在不同的环境下是不同的.

## get和post有什么区别?
> 都是HTTP协议中的请求方法。底层实现都是基于TCP/IP协议。

>- GET是通过明文发送数据请求,而POST是通过密文;
>- GET传输的数据量有限,因为url的长度有限,POST则不受限;
>- GET请求的参数只能是ASCII码,所以中文需要URL编码,而POST请求传参没有这个限制
>- GET产生一个TCP数据包;POST产生两个TCP数据包。对于GET方式的请求,浏览器会把http、header和data—并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

## 请指出document.onload和document.ready两个事件的区别?
> 执行时间
>- 一是ready,表示文档结构(dom树结构)已经加载完成(不包含图片等非文字媒体文件),
>- 二是onload,表示页面包含图片等文件在内的所有元素都加载完成。

> 执行个数不同
>- window.onload不能同时编写多个,如果有多个window.onload方法,只会执行一个
>- document.ready()可以同时编写多个,==并且都可以得到执行==

## 请解释JSONP的工作原理,以及它为什么不是真正的AJAX?
>- JSONP(JSON with Padding)是一个简单高效的跨域方式,HTML中的script标签可以加载并执行其他域的javascript,于是我们可以通过script标记来动态加载其他域的资源。例如我要从域A的页面pageA加载域B的数据,那么在域B的页面pageB中我以JavaScript的形式声明pageA需要的数据,然后在pageA中用script标签把pageB加载进来,那么pageB中的脚本就会得以执行。JSONP在此基础上加入了回调函数,pageB加载完之后会执行pageA中定义的函数,所需要的数据会以参数的形式传递给该函数。JSONP易于实现,但是也会存在一些安全隐患,如果第三方的脚本随意地执行,那么它就可以篡改页面内容,截获敏感数据。但是在受信任的双方传递数据,JSONP是非常合适的选择。
>- AJAX是不跨域的,而JSONP是一个是跨域的,还有就是二者接收参数形式不一样!

>- JSONP的主要优势在于对浏览器的支持较好;虽然目前主流浏览器支持CORS,但IE10以下不支持CORS。
>- JSONP只能用于获取资源(即只读,类似于GET请求);CORS支持所有类型的HTTP请求,功能完善。(这点JSONP被完虐,但大部分情况下GET已经能满足需求了)
>- JSONP的错误处理机制并不完善,我们没办法进行错误处理;而CORS可以通过onerror事件监听错误,并且浏览器控制台会看到报错信息,利于排查。
>- JSONP只会发一次请求;而对于复杂请求,CORS会发两次请求。

## 通过new创建一个对象的时候,构造函数内部有哪些改变?手动实现一个new
“`
function Person(){}
Person.prototype.friend =[];
Person.prototype.name = “”;
var a = new Person();
a.friend[0] = ‘杨爽’;
var b = new Person();
console.log(b.friend); //Array [“杨爽”]
“`
>- 创建一个空对象,并且this变量引用该对象,同时还继承了该函数的原型。
>- 属性和方法被加入到this 引用的对象中。
>- 新创建的对象由this所引用,并且最后隐式的返回this。

>- 手动实现一个new
“`
function mynew(Func, …args) {
// 1.创建一个新对象
const obj = {};
// 2.新对象原型指向构造函数原型对象
obj.__proto__ = Func.prototype;
// 3.将构建函数的this指向新对象
let result = Func.apply(obj, args);
// 4.根据返回值判断
return result instanceof Object ? result : obj;
}
“`

## 如何防范CSRF攻击,XSS攻击?
> xss攻击:是一种注入代码攻击,通过在网站里注入script代码,当访问者浏览网站的时候通过注入的script代码窃取用户信息,盗用用户身份等

> CSRF:跨站点请求伪造,攻击者盗用已经认证过的用户信息,以用户信息的名义进行操作(转账,购买商品等),由于身份已经认证过了,所以网站会认为此操作是用户本人操作。 CSRF并不能拿到用户信息,但它可以盗用用户的凭证进行操作。

– xSS攻击的防范
>- HttpOnly 防止劫取 Cookie
>- 输入检查-不要相信用户的所有输入
>- 输出检查-存的时候转义或者编码

– CSRF攻击的防范
>- 验证码
>- Referer Check
>- 添加token 并指定httponly来防止js被读取,也可以secure来保证token只在https下传输

## 箭头函数与普通函数的区别?
>- 箭头函数是在es6或更高版本中编写函数表达式的简明方法。
>- 箭头函数没有实例,不能作为构造函数,不能使用new
>- 箭头函数不绑定arguments super 或 new.target关键字,取而代之用展开运算符代替,’…’rest参数
>“`
> // 常规函数
> function test1(a){
> console.log(arguments);
> }
> test(1) // 1
> // 箭头函数
> var test2 = (a) =>{
> console.log(arguments)
> }
> //ReferenceError: arguments is not defined
> let test3 = (…a) =>{
> console.log(a[1])
> }
> test3(33,22,44); // 22
>“`
>- 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值,箭头函数的this始终指向函数定义时的this,而非执行时,并且为父作用域的this
>- 箭头函数通过call()或apply()bind()方法调用一个函数时,只传入了一个参数,对this并没有影响,仍然指向调用它的对象
>- 箭头函数没有prototype原型属性.
>- 返回对象字面量需要用小括号包裹起来
>- 如果箭头函数被非箭头函数包含,则this绑定的是最后一层非箭头函数的this,否则,this为undefined,因为指向的是window
>“`
> let fun5 = ()=> ({ foo: x })
> //如果 x => { foo: x } //则语法出错
>“`
>- 不可以使用yield命令,因此箭头函数不能用作Generator(生成器)函数。

## 说一下js继承?(6种)
– ES5中的继承有:
>- 原型链继承:原型链继承通过修改子类的原型为父类的实例,从而实现子类可以访问到父类构造函数以及原型上的属性或者方法。缺点:父类构造函数中的引用类型(比如对象/数组),会被所有子类实例共享。如果其中一个子类对实例进行修改,会导致所有其他子类实例的这个值都会改变,还有就是在创建子类型的时候不能向超类传递参数

“`
function Person() {
this.name = “wang”;
}
function Boy() {
this.age = “20”;
}
Boy.prototype = new Person(); //Boy继承了Person,通过原型,形成链条
var boy = new Boy();
console.log(boy.name); //wang
“`
>- 借用构造函数继承:构造函数继承其实就是通过修改父类构造函数this实现的继承。我们在子类构造函数中执行父类构造函数,同时修改父类构造函数的this为子类的this。
>- 优:解决了原型链继承中构造函数引用类型共享的问题,同时可以向构造函数传参(通过call传参)
缺:所有方法都定义在构造函数中,每次都需要重新创建(对比原型链继承的方式,方法直接写在原型上,子类创建时不需要重新创建方法)
>“`
> function SuperType(name) {
> this.name = name;
> this.sayName = function() {
> window.alert(this.name);
> };
> }
> function SubType(name, age) {
> SuperType.call(this, name); //在这里借用了父类的构造函数,通过调用call将this指向SubType 子类实例
> this.age = age;
> }
>“`
>- 组合继承:是结合两种继承方式,用构造函数方式继承属性,原型链方式继承方法
“`
function Parent() {
this.msg = ‘hello world’
}
Parent.prototype.getMsg = function() {
console.log(this.msg)
}
function Child() {
Parent.call(this)
this.msg2 = ‘Hi’
}
Child.prototype = new Parent()
// 需要重新设置子类的constructor,Child.prototype = new Parent()相当于子类的原型对象完全被覆盖了
Child.prototype.constructor = Child
var child=new Child
child.getMsg()//hello world
console.log(child.msg2)//Hi

//如果没有将Child.prototype的原型覆盖,则
//child.getMsg()//Hi
//console.log(child.msg2)//Hi
“`
– ES6有class继承:
>- class就相当于ES5中的构造函数
>- class中定义的方法后不能加function ,全部定义在class的prototype属性中
>- class默认为严格模式
“`
class Parent {
constructor() {
this.msg = ‘hello world’
}

getMsg() {
console.log(this.msg)
}
}

class Child extends Parent {
constructor() {
super()
this.msg2 = ‘Hi’
}
}

const child = new Child()
console.log(child.msg2)//Hi
child.getMsg() // hello world
“`
寄生组合继承
>- 寄生组合继承其实就是解决了父类构造函数调用两次的问题,使用父类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。
“`
function Person(name) {
this.name = name;
}

Person.prototype.sayName = function() {
console.log(“My name is ” + this.name + “.”);
};

function Student(name, grade) {
Person.call(this, name);
this.grade = grade;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.sayMyGrade = function() {
console.log(“My grade is ” + this.grade + “.”);
};
“`
原型式继承
>- 原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5中定义的Object.create()方法就是原型式继承的实现。缺点与原型链方式相同。

寄生式继承
>- 寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。

## JS 中的主要有哪几类错误?
> JS有三类的错误:
>- 加载时错误:加载web页面时出现的错误(如语法错误)称为加载时错误,它会动态生成错误。
>- 运行时错误:由于滥用HTML语言中的命令而导致的错误。
>- 逻辑错误:这些错误是由于对具有不同操作的函数执行了错误的逻辑而导致的

## JS中的Array.splice()和Array.slice()方法有什么区别?
> slice和splice虽然都是对于数组对象进行截取,但是二者还是存在明显区别,
函数参数上slice和splice第一个参数都是截取开始位置,slice第二个参数是截取的结束位置(不包含)而splice第二个参数(表示这个从开始位置截取的长度),
slice不会对原数组产生变化,而splice会直接剔除原数组中的截取数据!

## undefined,null 和 undeclared 有什么区别?
>- null表示”没有对象”,即该处不应该有值,转为数值时为0。典型用法是:
作为函数的参数,表示该函数的参数不是对象。
作为对象原型链的终点。
typeof(null) == object
typeof(undefined) == undefined
>- undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义,转为数值时为NaN。典型用法是:
变量被声明了,但没有赋值时,就等于undefined。
调用函数时,应该提供的参数没有提供,该参数等于undefined。
对象没有赋值的属性,该属性的值为undefined。
函数没有返回值时,默认返回undefined。

>- undeclared: js语法错误,没有在作用域申明直接使用,js无法找到对应的上下文。我们可以使用typeof的安全防范机制来避免报错,因为对于undeclared(或者not defined)变量,typeof会返回 “undefined”。

## JS中的高阶函数?
>- 高阶函数是JS函数式编程的最佳特性。它是以函数为参数并返回函数作为结果的函数。一些内置的高阶函数是map、filter、reduce等等。

## 区分声明函数和表达式函数?
“`
// 声明函数
function hello() {
return “HELLO”
}
// 表达式函数
var h1 = function hello() {
return “HELLO”
}
“`

## JS中的严格模式是什么以及如何启用以及作用
– 严格模式是在代码中引入更好的错误检查的—种方法。
>- 当使用严格模式时,不能使用隐式声明的变量,或为只读属性赋值,或向不可扩展的对象添加属性
>- 可以通过在文件,程序或函数的开头添加”use strict”来启用严格模式

>- ==作用==
> 1. 消除js不合理,不严谨地方,减少怪异行为
> 2. 消除代码运行的不安全之处
> 3. 提高编译器的效率,增加运行速度
> 4. 为未来的js新版本做铺垫。

>- ==特点==
> 1. 变量必须声明
> 2. 对象不能出现重复属性名
> 3. arguments改变,不会影响函数参数
> 4. eval,arguments变为关键字,不能作为变量名
> 5. 不允许使用with
> 6. 不用call,apply,bind改变this指向,一般函数调用指向null

## event loop(事件循环、事件轮询)的机制,画图?
– 同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
– 异步任务指的是,不进入主线程、而进入”任务队列”的任务,只有等主线程任务执行完毕,”任务队列”的任务才会进入主线程执行。

– js在执行过程中存在执行栈:
>- 什么是执行栈:当我们调用一个方法的时候,js会生成一个与这个方法对应的执行环境(context),又叫执行上下文。这个执行环境中存在着这个方法的私有作用域,上层作用域的指向,方法的参数,这个作用域中定义的变量以及这个作用域的this对象。 而当一系列方法被依次调用的时候,因为js是单线程的,同一时间只能执行一个方法,于是这些方法被排队在一个单独的地方。这个地方被称为执行栈。遵循先进先出原则

>- 当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为任务队列。

> js是单线程,但是它有个事件队列机制来处理异步操作
>- 任务队列中有包含宏任务和微任务。像常用的setTimeout,setInterval就是宏任务,Promise()的.then(()=>{})/.catch(()=>{})/.finally(()=>{}) 回调都是微任务。js先执行主线程任务,然后看如果有可以执行的微任务就会被执行,再看如果有宏任务就被执行,执行完继续主线程任务-然后就一遍一遍这循环执行,这也就是它的事件循环机制

> 事件循环(event loop)的执行原理如下:
>- 同步代码,一行一行执行,是放在主线程执行栈中执行的
>- 遇到异步代码,先记录下在web API中,等待时机(定时、网络请求等)
>- 时机到了,就移动到任务队列中
>- 如果主线程为空(即同步代码执行完毕) 事件循环开始工作
>- 轮询查找任务队列是否有任务,如有则移动到主线程执行
>- 然后继续轮询查找,直到完成

>- 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table并注册函数。
当指定的事情完成时,Event Table会将这个函数移入Event Queue。
主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
上述过程会不断重复,也就是常说的Event Loop(事件循环)。

2. DOM事件和event loop
>- js是单线程的
>- 异步(setTimeout、ajax等)使用回调,是基于event loop的DOM事件也使用回调,也基于event loop

## 什么是宏任务和微任务,两者有什么区别?
>- 宏任务:setTimeout、setInterval、requestAnimationFrame(node没有)、setImmediate(浏览器没有),requestAnimationFrame,ui渲染,i/o,postmessage
>- 微任务:promise(语法糖:async/await).then catch finally、MutationObserver(监视对DOM树所做的更改 node没有)、process.nextTick(比setTimeout更严格的延迟调用 允许用户处理error,清除不需要的资源,或者在事件循环前再次尝试请求 浏览器没有)
>- ==微任务执行时机要比宏任务早==

>- 宏任务和微任务的区别?
宏任务:DOM渲染后触发,如setTimeout
微任务:DOM渲染前触发,如promise
浏览器先执行微任务,再执行宏任务

## 解释一下什么是promise?
> promise是js中的一个对象,用于生成可能在将来产生结果的值.值可以是已解析的值,也可以是说明为什么未解析该值的原因.一般用于处理异步操作,其对象状态不受外界影响,一旦状态改变,就不会再变,任何时候都可以得到这个结果,
>- promise是构造函数,接受一个函数作为参数,返回一个promise实例
>- promise 可以有三种状态:
>- pending:初始状态,就是初始化Promise时,调用executor执行器函数后的状态。既不是成功也不是失败.
>- resolved:意味着操作完全成功
>- rejected:意味着操作失败
> 一个等待状态的promise对象能够成功后返回一个值,也能失败后带回一个错误
> 当这两种情况发生的时候,处理函数会排队执行,通过then方法会被调用.

## promise有哪三种状态?如何变化
>- promise有三种状态:pending、resolved、rejected
>- pending->resolved、pending->rejected变化是不可逆的

>- 状态的表现:
pending状态,不会触发then和catch
resolved状态,会触发后续的 then 回调函数,这个回调函数属于微任务
rejected状态,会触发后续的catch回调函数

>- then和catch改变状态
then正常返回resolved,里面有报错则返回rejected
catch正常返回resolved,里面有报错则返回rejected

>- 解决问题
> 回调地狱,代码难以维护,常常第一个的函数的输出是第二个函数的输入这种现象
> promise可以支持多个并发的请求,获取并发请求中的数据

## async/await ?
>async/await是同步语法,解决异步回调callback hell问题,promise then catch链式调用,但也是基于回调函数的。

>async/await和 promise的关系:
>- async/await 是解决异步回调的,但和promise并不互斥,两者相辅相成。
>- 执行async函数,返回的是promise对象
>- await 相当于promise的then
>- try…catch可捕获异常,代替了promise的catch

使用格式:
“`
async function aa(){
await ‘任务1’
await ‘任务2’
}
“`
> 1、对于async放在函数前面表明这个函数总是返回一个promise,如果代码中有return <非promise>语句,JavaScript会自动把返回的这个value值包装成promise的resolved值。
2、关键词await可以让JavaScript进行等待,直到一个promise执行并返回它的结果,JavaScript才会继续往下执行,如果不是promise的返回则会自动处理成promise对象
3、函数执行到(await)行会‘暂停’,不再往下执行,当promise处理完成后重新恢复运行

>- 关于await使用中错误的处理:
> 如果不对await进行错误处理,则会阻断程序执行。
await使用中,如果promise对象返回resolve,则返回什么值就是什么值,包括undefined。
但是如果返回reject,则返回值返回undefined,从catch函数中可以接收到。

解决办法:1.try-catch
“`
// 定义一个函数,返回promise对象
function Async(){
return new Promise((resolve,reject) => {
const a = 0;
if(a){
resolve(1)
}else{
reject(2)
}
})
}

async function hello () {
// 判断是否有报错
try{
const res = await Async()
console.log(res)
}catch(err){
console.log(“err”)
console.log(err)
}
}

this.hello()//err 2
/*我们用catch接收返回的错误,否则无法捕获错误信息,缺点在于本身try-catch是处理同步报错的问题,
如果await函数之后有报错,则无法判断报错来源*/

//优点:所有报错都到catch里面处理,不需要对await返回值进行判断
“`
2. .catch解决
“`
// 定义一个函数,返回promise对象
function Async(){
return new Promise((resolve,reject) => {
const a = 0;
if(a){
resolve(1)
}else{
reject(2)
}
})
}
async function hello () {
// 判断是否有报错
const res = await Async().catch(err =>{
console.log(“err”)
console.log(err)
})
if(res){
//do some
}
}

hello();

/*针对返回的promise使用.catch进行错误的接收,缺点是需要对返回值再进行一次判断,但是看起来也比上面的
try-catch方法来的更优雅和简洁*/
“`

## event loop 和 DOM渲染
>- js是单线程的,而且和DOM渲染共用一个线程
>- js执行的时候,得留一些时机供DOM渲染

> 执行流程:
>- call stack空闲
>- 尝试 DOM渲染
>- 触发event loop
>- 每次call stack 清空(即每次轮询结束),即同步任务执行完
>- 都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
>- 然后再去触发下一次event loop

## 点击穿透
> 假如页面上有两个元素A和B。B元素在A元素之上。我们在B元素的touchstart事件上注册了一个回调函数,该回调函数的作用是隐藏B元素。我们发现,当我们点击B元素,B元素被隐藏了,随后,A元素触发了click事件。

> 这是因为在移动端浏览器,==事件执行的顺序是touchstart > touchend > click==。而click事件有300ms的延迟,当touchstart事件把B元素隐藏之后,隔了300ms,浏览器触发了click事件,但是此时B元素不见了,所以该事件被派发到了A元素身上。如果A元素是一个链接,那此时页面就会意外地跳转。

## 移动端点击延迟
> 移动端点击有300ms的延迟是因为移动端会有双击缩放的这个操作,因此浏览器在click之后要等待300ms,看用户有没有下一次点击,来判断这次操作是不是双击。

## 变量提升及其表现和原因
> 变量提升的表现是,无论我们在函数中何处位置声明的变量,好像都被提升到了函数的首部,我们可以在变量声明前访问到而不会报错。

> 原因:js引擎在代码执行前有一个解析的过程,创建了执行上下文,初始化了一些代码执行时需要用到的对象。当我们访问一个变量时,我们会到当前执行上下文中的作用域链中去查找,而作用域链的首端指向的是当前执行上下文的变量对象,这个变量对象是执行上下文的一个属性,它包含了函数的形参、所有的函数和变量声明,这个对象的是在代码解析的时候创建的。

## var、let、const、function的总结
> var:在ES5中,顶层对象的属性和全局变量是等价的,用var声明的变量既是全局变量,也是顶层变量。注意:顶层对象,在浏览器环境指的是window对象,在Node指的是global对象
>- 允许二次定义
>“`
>var x = 10;console.log(x);//10
>var x = 20;console.log(x);//20
>“`
>- 存在变量提升
>“`
>var x = 10;
>function test(){
> console.log(x);//undefined
> var x = 10;
> }
>test();
>“`
> 解释:变量创建步骤细分为:创建>初始化>赋值
当调用test函数,进入一个执行环境,收集变量对象(变量,test函数内的函数),提升到该环境顶端,创建var定义的变量x,同时初始化为undefined。
所以var变量提升包括创建和初始化undefined。

>- 污染全局作用域
>“`
>for(var i =1;i<5;i++){} >console.log(window.i);//5
>“`
>- 隐式变量不存在提升
>“`
> console.log(a);//Uncaught ReferenceError: a is not defined
> a = ‘aaa’;
>“`

>- 函数中使用var时候变量是局部的,但是没有使用声明字段的时候则是全局的:
“`
//函数中声明
var a = 20
function change(){
var a = 30
}
change()
console.log(a) //20

//函数中无声明
var a = 20
function change(){
a = 30
}
change()
console.log(a) //30
“`
> let
>- 存在块级作用域(es6语法 所声明的变量只在let命令所在的代码块内有效)
>“`
> { let x = 10;} console.log(x);//x is not defined
>“`
>- 暂时性死区(不存在变量提升)
> ==暂时性死区:变量必须先定义再使用。==
>“`
> var x =10;
>function test(){
> console.log(x);//Cannot access ‘x’ before initialization
> let x = 20;
>}
>test();
>“`
> 打印x报错,提示想要访问x必须先初始化。
当调用test函数,进入一个执行环境,收集变量对象(变量,test函数内的函数),提升到该环境顶端,创建let定义的变量x,但let定义的变量只有[创建]阶段提升了,[初始化]阶段没提升,而没初始化就不给访问,报错。
let 存在提升,只不过是[创建]阶段提升了。

>- 解决延迟打印问题
>“`
>for(var i=1;i<5;i++){ > setTimeout(()=>{
> console.log(i);
> },1000);
> }//打印4个5
>for(let i=1;i<5;i++){ > setTimeout(()=>{
> console.log(i);
> },1000);
> }//打印1 2 3 4
>“`
> for循环执行了4次,一共创造了4个执行环境,每个执行环境都有对应的i,但是var定义的i没有块级作用域的概念,所以i都是定义在全局上的,而let定义的i都是保存在块级作用域内的。
>- let,const不允许重复定义
>“`
>{ let x = 5;let x = 10;}//Uncaught SyntaxError: Identifier ‘x’ has already >been declared
>{let x = 5;var x =10;}//Uncaught SyntaxError: Identifier ‘x’ has already >been declared
>{let x = 5;const x =10;}//Uncaught SyntaxError: Identifier ‘x’ has already >been declared
>{const x = 5;let x = 5;}//Uncaught SyntaxError: Identifier ‘x’ has already >been declared
>“`
>- let用于创建一个可变变量,可变变量是像var这样的普通变量,可以任意次数地更改。

> const声明一个只读的常量,一旦声明,常量的值就不能改变,不能重复声明
>- const声明必须带着初始化
>“`
> const x;
> x = 5;
> console.log(x);//Uncaught SyntaxError: Missing initializer in const declaration
>“`
>- const只能初始化一次
>“`
> const x = 10;
> x = 5;
> console.log(x);//Uncaught TypeError: Assignment to constant variable.
>“`
> const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动,我们知道,数据的基本类型是存在栈中的,数据的引用类型是存放在堆中的,因此当用const去声明一个对象的内存地址时候,只要保证对象的指向地址不变,对于对象中的数据改变,并不会引起报错
> “`
> const obj = {
> name:’san’,
> age:22
> };
> obj.age = 100;
> console.log(obj.age);//100
> “`
>- 关于const的变量提升
> “`
> var x = 10;
> function test(){
> console.log(x);
> const x = 45;
> }
> test(); //Uncaught ReferenceError: Cannot access ‘x’ before initialization
> “`
> const只有[创建]和[初始化],当进入执行上下文,收集变量对象时,只有[创建]这个阶段被提前了.所以提示未初始化不能访问x.
> 用于创建一个不可变变量。不可变变量是指其值在程序的整个生命周期中永不改变的变量。

> function
>- 函数提升
> “`
> function test(){
> fn();//fn被执行了
> console.log(fn);//fn(){console.log(‘fn被执行了’)}
> function fn(){
> console.log(‘fn被执行了’);
> }
> }
> test();
> “`
> 函数提升是函数名和函数体都提升了。
>- 重复定义函数
> 重复定义的函数还是使用原来分配的内存。
> “`
> function test(){
> console.log(‘test1’);
> }
> function test(){
> console.log(‘test2’);
> }
> test();//test2
> “`
>- 变量提升和函数提升
> 函数提升优先级比变量提升要高,且不会被变量声明覆盖,但是会被变量赋值覆盖。
> “`
> console.log(a);//ƒ a(){}
> var a = 10;
> function a(){}
> console.log(a);//10
> “`
> 等同于
> “`
> function a(){}
> var a;
> console.log(a);//ƒ a(){}
> a = 10;
> console.log(a);//10
> “`

## js反转一个字符串
“`
‘hello world’.split(”).reverse().join(”) //reverse关键
//首先将字符串拆分为数组,然后反转数组,最后将字符连接起来形成字符串.
“`
>- 第二种: 使用循环:首先,计算字符串的字符数,然后对原始字符串应用递减循环,该循环从最后一个字符开始,打印每个字符,直到count变为零.

## Promise的all和race(修改)
> all是可迭代对象里面所有的都变成resolved才会执行下一步,race是只要有一个resolved,就会执行下一步

## 判断一个对象是否为空的方法:
>- 使用ES6新增语法:Object.keys(testObj)返回一个array类型,里面是testObj中用户定义的键值。
>- 使用JSON.stringify
>“`
> if(JSON.stringify(testObj)=='{}’){
> console.log(‘是空对象!’)
> }
>“`
>- 使用for…in遍历,能够遍历到任何内容的就不为空

## 为什么0.1 + 0.2 != 0.3
> 当计算机计算0.1+0.2的时候,实际上计算的是这两个数字在计算机里所存储的二进制,0.1和0.2在转换为二进制表示的时候会出现位数无限循环的情况。js中是以64位双精度格式来存储数字的,只有53位的有效数字,超过这个长度的位数会被截取掉这样就造成了精度丢失的问题。

> 对于这样的情况,我们可以将其转换为整数后再进行运算,运算后再转换为对应的小数,以这种方式来解决这个问题。

> 我们还可以将两个数相加的结果和右边相减,如果相减的结果小于一个极小数,那么我们就可以认定结果是相等的,这个极小数可以使用es6的Number.EPSILON

## Number() 的存储空间是多大
>- 2的53次方,如果超出的话,会发生截断

## 构造函数和普通函数的区别
>- 构造函数也是一个普通函数,创建方式和普通函数一样,但构造函数习惯上首字母大写
>- 调用方式不一样

>- 构造函数的执行流程以 new Func()为例
> 1. 立刻在堆内存中创建一个新的对象 {}
> 2. 将新建的对象设置为函数中的this Func.call和apply
> ({})
> 3. 逐个执行函数中的代码 {}.name=xxx; {}.methods = xxx
> 4. 将新建的对象作为返回值 return {}

“`
function person(){}
let per = person()
console.log(per) // 返回值是”undefined”
function Person(){}
let per = new Person()
console.log(per) // 返回值是”[object object]”对象
“`

## 正则表达式
>- 常用的正则表达式方法有:

> 1. search()方法
>- 用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串,并返回子串的起始位置。
>“`
>str.search(/Runoob/i); // i表示不区分大小写
>“`

> 2. replace()方法
>- 用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
>“`
> var txt = str.replace(/microsoft/i,”Runoob”);
>“`

> 3. test()方法
>- 用于检测一个字符串是否匹配某个模式,如果字符串中含有匹配的文本,则返回 true,否则返回 false。
>“`
>var patt = /e/;
>patt.test(“The best things in life are free!”); // true
>“`

## Array.map和Array.forEach
> foreach()方法会针对每一个元素执行提供得函数,该方法没有返回值,是否会改变原数组取决与数组元素的类型是基本类型还是引用类型
map()方法不会改变原数组的值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值

## 说说写JavaScript的基本规范?
1. 不要在同一行声明多个变量
2. 使用 ===或!==来比较true/false或者数值
3. switch必须带有default分支
4. 函数应该有返回值
5. for if else 必须使用大括号
6. 语句结束加分号
7. 命名要有意义,使用驼峰命名法
8. 一个函数作用域中所有的变量声明应该尽量提到函数首部,声明时如果变量没有值,应该给该变量赋值对应类型的初始值,便于他人阅读代码时,能够一目了然的知道变量对应的类型值。
9. 不要在内置对象的原型上添加方法,如Array,Date。

## jQuery使用建议
>- 尽量减少对dom元素的访问和操作
>- 尽量避免给dom元素绑定多个相同类型的事件处理函数,可以将多个相同类型事件处理函数合并到一个处理函数,通过数据状态来处理分支
>- 尽量避免使用toggle事件

## JSON了解
>- 全称:JavaScript Object Notation

>- JSON中对象通过“{}”来标识,一个“{}”代表一个对象,如{“AreaId”:”123”},对象的值是键值对的形式(key:value)。JSON是JS的一个严格的子集,一种轻量级的数据交换格式,类似于xml。可以被任何的编程语言读取和作为数据格式来传递,数据格式简单,易于读写,占用带宽小。

>- 注意:JSON中属性值不能为函数,不能出现NaN这样的属性值等

> 两个函数:
>- JSON.parse(str)
解析JSON字符串 把JSON字符串变成JavaScript值或对象
>- JSON.stringify(obj)
将一个JavaScript值(对象或者数组)转换为一个 JSON字符串

## 页面编码和被请求的资源编码如果不一致如何处理?
>- 若请求的资源编码,如外引js文件编码与页面编码不同。
>- 可根据外引资源编码方式定义为 charset=”utf-8″或”gbk”。
比如:http://www.yyy.com/a.html 中嵌入了一个http://www.xxx.com/test.js
a.html 的编码是gbk或gb2312的。 而引入的js编码为utf-8的 ,那就需要在引入的时候
>“`
>
>“`

## 模块化开发怎么做?
> 模块化开发指的是在解决某一个复杂问题或者一系列问题时,依照一种分类的思维把问题进行系统性的分解。
> 模块化是一种将复杂系统分解为代码结构更合理,可维护性更高的可管理的模块方式。
>- 对于软件行业:系统被分解为一组高内聚,低耦合的模块。

> 发展历程:最原始的写法是使用函数来作为模块,几个函数作为一个模块,但是这种方式容易造成全局变量的污染,并且模块间没有联系。后面提出了对象写法,通过将函数作为一个对象的方法来实现,这样解决了直接使用函数作为模块的一些缺点,但是这种办法会暴露所有的所有的模块成员,外部代码可以修改内部属性的值。现在最常用的是立即执行函数的写法,通过利用闭包来实现模块私有作用域的建立,同时不会对全局作用域造成污染。

> 1. 定义封装的模块
> 2. 定义新模块对其他模块的依赖
> 3. 可对其他模块的引入支持。在JavaScript中出现了一些非传统模块开发方式的规范。
>- CommonJS的模块规范 (同步方式)
>- AMD(Asynchronous Module Definition)(异步加载)
>- CMD(Common Module Definition)(异步加载)
>- AMD是异步模块定义,所有的模块将被异步加载,模块加载不影响后边语句运行。所有依赖这个模块的语句都定义在一个回调函数里,等到加载完成后再执行回调函数。

## AMD(Asynchronous Module Definition)、CMD(Common Module Definition)规范区别?
> AMD 是 RequireJS 在推广过程中对模块定义的规范化产出
> CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。

>- 区别:
> 1. 在于模块的执行时机,AMD在依赖模块加载完成后就直接执行依赖模块,依赖模块的执行顺序和我们书写的顺序不一定一致。而CMD在依赖模块加载完成后并不执行,只是下载而已,等到所有的依赖模块都加载好后,进入回调函数逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序就和我们书写的顺序保持一致了。
> 2. 第一个方面是在模块定义时对依赖的处理不同。AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块。而CMD推崇就近依赖,只有在用到某个模块的时候再去require
“`
// CMD
define(function(require, exports, module) {
var a = require(‘./a’)
a.doSomething()
// 此处略去 100 行
var b = require(‘./b’) // 依赖可以就近书写
b.doSomething()
})
// AMD 默认推荐
define([‘./a’, ‘./b’], function(a, b) { // 依赖必须一开始就写好
a.doSomething();
// 此处略去 100 行
b.doSomething();
})
“`

## es6模块与commonJs有什么异同?
>- commonJs主要使用require()和module.exports来引入和导出各种值,这种模块加载方案是服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式加载没有问题。
>- es6模块主要是import和export来引入和导出各种值
>- commonJS模块输出的是一个值的拷贝,模块内部的变化影响不到这个值,而ES6模块输出的是值的引用,在脚本运行时候,会根据这个引用到被加载的模块取值;
>- 关于模块顶层的this指向问题,在commonJS顶层,this指向当前模块;而在ES6模块中,this指向undefined;
>- CommonJS模块是运行时加载,ES6模块是编译时输出接口。CommonJS模块就是对象,即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。而ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

## requireJS的核心原理是什么?
> 通过动态创建script脚本来异步引入模块,然后对每个脚本的load事件进行监听,如果每个脚本都加载完成了,再调用回调函数。

## call和apply和bind之间的异同
> call()方法和apply()方法和bind()的作用相同,动态的修改当前函数内部环境对象this的指向。都不会修改原先函数的this指向.

> apply定义:调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数
>- 语法: fun.apply(thisArg, [argsArray])
> 1. thisArg:在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
> 2. argsArray:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。

> call定义:类似apply
>- 语法:fun.call(thisArg, arg1, arg2)
> 1. call方法接受的是若干个参数列表
> 2. 不传参数,或者传null,undefined,函数中的this指向window对象

> bind定义:创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

> 不同点:
> 1. 执行方式不同:
>- call和apply是改变后页面加载之后就立即执行,是同步代码。
>- bind是异步代码,改变后不会立即执行;而是返回一个新的函数。
> 2. 传参方式不同:
>- call和bind传参是一个一个逐一传入,不能使用剩余参数的方式传参。
>- apply可以使用数组的方式传入的,只要是数组方式就可以使用剩余参数的方式传入。

– 应用场景:
>1. call经常做继承
>2. apply经常跟数组有关系,比如借用Math对象对数组进行处理
>3. bind不调用函数,但是想改变this的指向,比如改变定时器内部的this指向

## 回流与重绘
> 当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。
> 每个页面至少需要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树。
> 完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘

## DOM操作
> 1. 创建新节点
“`
createDocumentFragment() //创建一个DOM片段
createElement()
//创建一个具体的元素(对于HTML网页大小写不敏感,即参数为div或DIV返回的是同一种节点;)
createTextNode() //创建一个文本节点
createAttribute() //创建属性节点
“`
> 2. 添加、移除、替换、插入
“`
appendChild() //把一个子节点添加到父节点的最后一个子节点
removeChild()
/*删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild把自己删掉,删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置*/
replaceChild()
insertBefore() //在已有的子节点前插入一个新的子节点
setAttribute(‘属性名’,’属性值’) //在指定元素中添加一个属性节点,如果元素中已有该属性改变属性值
“`
> 3. 查找
“`
document.getElementById(‘id属性值’);返回拥有指定id的对象的引用
document.getElementsByClassName(‘class属性值’);返回拥有指定class的对象集合
document.getElementsByTagName(‘标签名’);返回拥有指定标签名的对象集合
document.getElementsByName(‘name属性值’); 返回拥有指定名称的对象结合
document/element.querySelector(‘CSS选择器’); 仅返回第一个匹配的元素
document/element.querySelectorAll(‘CSS选择器’); 返回所有匹配的元素
document.documentElement; 获取页面中的HTML标签
document.body; 获取页面中的BODY标签
document.all[”]; 获取页面中的所有元素节点的对象集合型

//传入任何有效的css 选择器,即可选中单个 DOM元素
document.querySelector(‘.element’)
document.querySelector(‘#element’)
document.querySelector(‘div’)

//返回一个包含节点子树内所有与之相匹配的Element节点列表,如果没有相匹配的,则返回一个空节点列表
const notLive = document.querySelectorAll(“p”);
“`
> 4.更新
“`
// innerHTML不但可以修改一个DOM节点的文本内容,还可以直接通过HTML片段修改DOM节点内部的子树
“`
> 属性操作
“`
getAttribute(key); //获取节点的属性,key是属性名称,比如ID,title,value等的值。
setAttribute(key, value);
hasAttribute(key); //key 需要判断的属性名,返回值为布尔值
removeAttribute(key) //key 要删除的属性的名称,没有返回值
“`

## 数组常用的方法?
> push(n):从数组的尾部添加一个元素n,返回这个添加的元素n
pop():从数组的尾部删除一个元素,返回这个删除的元素,不接收参数
unshift(n):从数组的头部添加一个元素n,返回这个添加的元素n,原数组发生改变
shift():从数组的头部删除一个元素,返回这个删除的元素,不接收参数
slice(start,end):返回数组中提取的子数组。这一对象是一个由start和end决定的(包括start,不包括end)。原始数组不会被改变。
splice(i,n,m,m,m,…):从数组下标i开始,删除项目n个,然后添加项目m。此方法会改变原数组。以数组形式返回被修改的内容.
reverse():数组翻转,该方法会改变原来的数组,而不会创建新的数组
sort():无参数默认从小到大排序,判断方式:按位判断,可以传入回调函数,如果函数返回值为正数,则交换两个参数位置
concat():数组拼接,返回拼接数组,不影响原数组
join(’-’):将数组转化为为字符串,以括号内的拼接
length 数组元素的长度
indexOf 返回在数组中可以找到一个给定元素的第一个索引(下标),如果不存在,则返回-1
lastIndexOf(searchEle,fromIndex) 返回指定元素在数组中的最后一个的索引(下标),如果不存在则返回-1。从数组的后面向前查找,第二个参数可选,如果有第二个参数从fromIndex处开始往前找。
toString():将数组转换为字符串,并返回结果,以逗号隔开
toLocalString():把数组转换为本地数组,并返回结果。

## 字符串常用的方法
>- 1. indexOf() 返回某个指定的子字符串在字符串中第一次出现的位置,如果找不到返回-1
>- 2. split(sep) 将字符串按照指定的字符切割成数组元素,sep表示指定的字符
>- 3. slice(start,end) 截取字符串,start开始的下标,end结束的下标,不包含end本身;如果end为空截取到最后,如果为负数表示倒数。
>- 4. substr(start,count) 截取字符串,start开始的下标,count截取的长度,如果count为空截取到最后,如果start为负值表示倒数
>- 5. substring(start,end) 截取字符串,start开始的下标,end结束的下标,不包含end本身,如果end为空截取到最后;如果下标为负数,自动转为0
>- 6. replace(regexp/substr,replacement),replace()方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。返回一个新的字符串,是用 replacement 替换了 regexp 的第一次匹配或所有匹配之后得到的。
>- 7. toLowerCase():把字符串转为小写,返回新的字符串。
>- 8. toUpperCase():把字符串转为大写,返回新的字符串。
>- 9. search():用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。
>- 10. trim():去掉字符串两边的空白
>- 11. startsWith():查看字符串是否以指定的字符串开头
>- 12. charCodeAt():返回指定的位置的字符的Unicode编码
## 什么是Cookie隔离(请求资源的时候不带cookie要怎么做)
> 通过使用多个非主要域名来请求静态文件
>- 如果静态文件都放在主域名下,那静态文件请求的时候带有的cookie的数据提交给server(服务器)是非常浪费的,还不如隔离开。
>- 因为cookie有域的限制,因此不能跨域提交请求,故使用非主要域名的时候,请求头中就不会带有cookie数据,这样可以降低请求头的大小,降低请求时间,从而达到降低整体请求延时的目的。
>- 同时这种方式不会将cookie传入server,也减少了server对cookie的处理分析环节,提高了server的http请求的解析速度。

## 鼠标响应事件?
>- onclick鼠标点击某个对象;
>- onfocus获取焦点;
>- onblur失去焦点;
>- onmousedown鼠标被按下

## flash和js通过什么类如何交互?
> Flash提供了ExternalInterface接口与JavaScript通信
> ExternalInterface有两个方法,call和addCallback
> call的作用是让Flash调用js里的方法,addCallback是用来注册flash函数让js调用

## Flash与Ajax各自的优缺点?
> Flash
>- 优点:适合处理多媒体、矢量图形、访问机器。
>- 缺点:对css、处理文本不足,不容易被搜索。

> Ajax
>- 优点:对css、文本支持很好,
>- 缺点:对多媒体、矢量图形、访问机器不足。

## 有效的javascript变量定义
> 第一个字符必须是一个字母、下划线”_”或一个美元符号($)
> 其他字符可以是字母、下划线、美元符号或数字

## XML与JSON的区别
> 数据体积方面:JSON相对于XML来讲,数据的体积小,传递的速度更快些。

> 数据交互方面:JSON与JavaScript的交互更加方便,更容易解析处理,更好的数据交互。

> 数据描述方面:JSON对数据的描述性比XML较差。

> 传输速度方面:JSON的速度要远远快于XML。

## HTML与XML的区别
> 1. XML用来传输和存储数据,HTML用来显示数据
> 2. XML使用的标签不用预先定义
> 3. XML标签必须成对出现
> 4. XML对大小写敏感
> 5. XML中空格不会被删减
> 6. XML中所有特殊符号必须用编码表示
> 7. XML中的图片必须有文字说明

## Web Worker和Web Socket
> web socket:在一个单独的持久连接上提供全双工、双向的通信。使用自定义的协议(ws://、wss://)
> 同源策略对web socket不适用。

> web worker:运行在后台的JavaScript,不影响页面的性能
>“`
> var worker = new Worker(url); //创建worker
> worker.postMessage(data); //向worker发送数据
> worker.onmessage; //接收worker返回的数据
> worker.terminate(); //终止一个worker的执行
>“`

## JS垃圾回收机制?
> 当一个变量的生命周期结束之后它所指向的内存就应该被释放。JS有两种变量,全局变量和在函数中产生的局部变量。局部变量的生命周期在函数执行过后就结束了,此时便可将它引用的内存释放(即垃圾回收),但全局变量生命周期会持续到浏览器关闭页面。回收方式:

> 1. 标记清除
>- 这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。
>- 大部分浏览器以此方式进行垃圾回收,当变量进入执行环境(函数中声明变量)的时候,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”,在离开环境之后还有的变量则是需要被删除的变量。

> 2. 引用计数
>- 这是最简单的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。
>- 该算法有个限制:无法处理循环引用。两个对象被创建,并互相引用,形成了一个循环。它们被调用之后不会离开函数作用域,所以它们已经没有用了,可以被回收了。然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。

## 如何删除一个cookie?
> 1. 将cookie的失效时间设置为过去的时间(expires)
“`
document.cookie = “user=”+ encodeURIComponent(“name”) + “expires=”+ new Date(0);
“`

> 2. 将系统时间设置为当前时间往前一点时间
“`
var date = new Date();
date.setDate(date.getDate()-1);
“`

## 数组去除重复的方法有哪些?
>- 使用Map方法
“`
let map = new Map();
let newStr = [];
for (let i = 0; i < arr.length; i++) { if (!map.has(arr[i])) { map.set(arr[i], true); newStr.push(arr[i]); } } console.log(newArr) // [1, 2, 4, null, "3", "abc", 3, 5] ``` >– 使用 set
“`
function uniquearray(array) {
let unique_array= Array.from(new Set(array))
return unique_array;
// 另一种写法[…new Set(arr)]
}
“`
> 利用sort去重(先sort排序,这是相同的都在一起,在用前一个比较后一个,不相等的时候,返回a.push。相等则不做任何操作,最后返回的数值都是不相同的)
“`
function cleanRepeat(arr) {
if(!Array.isArray(arr)) {
console.log(‘Type error’);
return
}
arr = arr.sort();
let array = [];
for(let i = 0; i < arr.length; i++) { if(arr[i] !== arr[i - 1]) { array.push(arr[i]) } } return array; } let arr1 = [1, 2, 3, 5, 4, 8, 7, 8, 5, 1,2, 5, 6, 9, 6, 2, 1, 5, 5] console.log(cleanRepeat(arr1)); // [1, 2, 3, 4, 5, 6, 7, 8, 9] ``` >– 使用filter(返回item第一次出现的位置等于当前的index的元素),不会改变原数组
“`
function unique_array (arr) {
let unique_array = arr.filter(function(elem, index, self) {
return index == self.indexOf(elem);
})
return unique_array;
}

console.log(unique_array(array_with_duplicates));
“`

>- 使用for循环(双for循环,用arr[0]第零个,跟后面每一项比较,相等,则删除,不相等,则继续循环。)
“`
function cleanRepeat(arr) {
for (let i = 0;i < arr.length; i++) { for (let j = i + 1; j < arr.length; j++) { if (arr[i] === arr[j]) { arr.splice(j, 1); j--; } } } return arr; } let arr1 = [1, 2, 3, 5, 4, 8, 7, 8, 5, 1,2, 5, 6, 9, 6, 2, 1, 5, 5]; console.log(cleanRepeat(arr1)); // [1, 2, 3, 5, 4, 8, 7, 6, 9] ``` >– 使用reduce方法(接收一个函数作为累加器,数组中的每个值从左到右开始计算,最终计算为一个值。)
“`
// 暂时空着 因为只能判断数值型数组
“`

>- 使用includes方法
“`
function cleanRepeat(arr) {
if(!Array.isArray(arr)) {
console.log(‘Type error’);
return
}
let array = [];
for(i = 0; i < arr.length; i++) { if(!array.includes(arr[i])) { array.push(arr[i]); } } return array; } let arr1 = [1, 2, 3, 5, 4, 8, 7, 8, 5, 1,2, 5, 6, 9, 6, 2, 1, 5, 5] console.log(cleanRepeat(arr1)); // // [1, 2, 3, 5, 4, 8, 7, 6, 9] ``` >– 使用for + indexOf方法(通过查找关键字,从newArr查找arr里面的关键字,当没有的时候返回的值是-1,代表不重复,再把返回-1的arr[i]添加到newArr)
“`
function cleanRepeat(arr) {
if (!Array.isArray(arr)) {
console.log(‘Type error’);
return
}
let newArr = [];
for(let i = 0; i < arr.length; i++) { if(newArr.indexOf(arr[i]) === -1) { newArr.push(arr[i]) } } return newArr } let arr1 = [1, 2, 3, 5, 4, 8, 7, 8, 5, 1,2, 5, 6, 9, 6, 2, 1, 5, 5] console.log(cleanRepeat(arr1)); // // [1, 2, 3, 5, 4, 8, 7, 6, 9] ``` >– 使用forEach + indexOf方法
“`
function cleanRepeat(arr) {
if (!Array.isArray(arr)) {
console.log(‘Type error’);
return
}
let newArr = [];
arr.forEach(val => {
if(newArr.indexOf(val) == -1) {
newArr.push(val);
}
})
return newArr
}
let arr1 = [1, 2, 3, 5, 4, 8, 7, 8, 5, 1, 2, 5, 6, 9, 6, 2, 1, 5, 5]
console.log(cleanRepeat(arr1)); // // [1, 2, 3, 5, 4, 8, 7, 6, 9]
“`

>- 单独使用indexof方法(搜索关键字默认得到的是角标,当有相同字时,默认得到第一个,得到的角标跟i相等,则代表没有重复的,不相等,代表重复,自动pass掉)
“`
var b=[]
for(var i=0;i– substr() 函数的形式为substr(startIndex,length) 它从startIndex返回子字符串并返回’length’个字符数
“`
var s = “hello”;
s.substr(1,4) == “ello” // true
“`
>- substring() 函数的形式为substring(startIndex,endIndex) 它返回从startIndex到endIndex – 1的子字符串
“`
var s = “hello”;
s.substring(1,4) == “ell” // true
“`

## Set、Map(es6)和Array的异同、用法
> map(字典)和set(集合)
>- 共同点:主要的应用场景在于数据重组和数据储存
>- Set:创建类似数组的数据结构,但成员是唯一(基本数据类型无重复,应用数据类型可以重复),只有key没有value,value就是key,是以[value,value]的形式储存元素.一般称为集合Set本身为一个构造函数,用来生成Set数据结构
> 常用方法:
“`
//操作方法:
add(value)//:新增,相当于 array里的push,返回Set结构本身,当添加实例中已经存在的元素,set不会进行处理添加。
delete(value)//:存在即删除集合中value,返回一个布尔值,表示删除是否成功。
has(value)//:返回一个布尔值,判断集合中是否存在 value。
clear()//:清空集合,没有返回值。

//遍历方法(遍历顺序为插入顺序)
keys()//:返回一个包含集合中所有键的迭代器。
values()//:返回一个包含集合中所有值得迭代器。
entries()//:返回一个包含Set对象中所有元素得键值对迭代器。
forEach(callbackFn,thisArg)//:用于对集合成员执行callbackFn(回调函数)操作,如果提供了thisArg参数,回调中的this会是这个参数,没有返回值。
“`

>- Map:以键值对的形式存储数据,key和value可以是任何数据类型(包括对象),Map本身是一个构造函数,用来生成Map数据结构
> 常用方法:
“`
//操作方法:
set(key, value) //向字典中添加新元素,如果key已经有值,则键值会被更新返回整个map,可采用链式写法。
get(key) //通过键查找特定的数值并返回,如果找不到key,返回undefined。
has(key) //判断字典中是否存在键key,返回布尔值。
delete(key) //通过键 key 从字典中移除对应的数据,成功返回true,失败返回false。
clear() //将这个字典中的所有元素删除,没有返回值。

//遍历方法:
Keys() //将字典中包含的所有键名以迭代器形式返回。
values() //将字典中包含的所有数值以迭代器形式返回。
entries() //返回所有成员的迭代器。
forEach() //遍历字典的所有成员。

“`

> Set/Map转Array:
>- Array.from(Set)
>- const arr = [ …Set ]
>- const arr = [ …Map.values ]

> 通过size可以获取Map和Set对象的长度
“`
let myMap = new Map([[“name”, “lle”], [“age”, 11]]);
let mySet = new Set(“hello”); //因为ll是重复的 在集合里
console.log( myMap.size); //2
console.log( mySet.size); //4
“`

## Map和Object的异同
> ①:Object的键只能是字符串或者Symbols,Map的键可以是任何类型。
②:Map中的键值遵循FIFO原则,即有序的。而Object添加的键则不是。
③:Map中的键值对可以通过size来计算,Object需要我们手动计算。
④:Object都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

## 类数组转成数组的方法
> 通过call调用数组的slice方法来实现转换
“`
Array.prototype.slice.call(arrayLike);
“`
> 通过call调用数组的splice方法来实现转换
“`
Array.prototype.splice.call(arrayLike,0);
“`
> 通过apply调用数组的concat方法来实现转换
“`
Array.prototype.concat.apply([],arrayLike);
“`
> 通过Array.from方法来实现转换
“`
Array.from(arrayLike);
“`
## 如何通过类别名获取dom元素
> 在 JS 中使用document.getElementsByClassName() 方法来获取具有类名的元素。

## 解释JS中的MUL函数
> mul表示数的简单乘法.在这种技术中,将一个值作为参数传递给一个函数,而该函数将返回另一个函数,将第二个值传递给该函数,然后重复继续.例如:x(‘*’)y(‘*’)z可以表示为
> “`
> function mul (x) {
> return function (y) {
> return function (z) {
> return x * y * z;
> }
> }
> }
> “`

## JS中如何将页面重定向到另一个页面?
> 1. 使用location.href: window.location.href = ‘https://www.xxxx.com’
> 调用此方法网页会被写入在浏览器历史记录中.

> 2. 使用location.replace: window.location.replace(“https://www.xxxx.com”)
> 将目前浏览器的地址替换掉,调用这个方法的网页,将不会被写入浏览记录。

## 列出JS中的一些设计模式:
> 设计模式是软件设计中常见问题的通用可重用解决方案,以下是一些设计模式:
* 发布订阅模式:
这种设计模式可以大大降低程序模块之间的耦合度,便于更加灵活的扩展和维护。
* 中介者模式 :
观察者模式通过维护一堆列表来管理对象间的多对多关系,中介者模式通过统一接口来维护一对多关系,且通信者之间不需要知道彼此之间的关系,只需要约定好API即可。
* 代理模式 :
为其他对象提供一种代理以控制对这个对象的访问。
代理模式使得代理对象控制具体对象的引用。代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西。
* 单例模式 :
保证一个类只有一个实例,并提供一个访问它的全局访问点(调用一个类,任何时候返回的都是同一个实例)。
* 工厂模式 :
工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一
个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型

* 装饰者模式 : 装饰者(decorator)模式能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责(方法或属性)。与继承相比,装饰者是一种更轻便灵活的做法。
## 如何在JS中动态添加/删除对象的属性?
> 可以使用object.property_name = value 向对象添加属性
> delete object.property_name 用于删除属性

“`
let user = new Object()
user.name = ‘demo’
user.age = 25
console.log(user)
delete user.age
console.log(user)
“`

## JS中=&=和===区别是什么?
>- 对于string,number等基础类型,=&=和===有区别
>- 1. 不同类型间比较,==之比较“转化成同一类型后的值”看“值”是否相等,===如果类型不同,其结果就是不等。
>- 2. 同类型比较,直接进行“值”比较,两者结果一样。

>- 对于Array,Object等高级类型,=&=和===没有区别
>- 1. 进行“指针地址”比较。

>- 基础类型与高级类型,=&=和===有区别
>- 1. 对于==,将高级转化为基础类型,进行“值”比较。
>- 2. 因为类型不同,===结果为false。

## JS中的匿名函数是什么?
>- 匿名函数:就是没有函数名的函数,如:
“`
(function(x, y){
alert(x + y);
})(2, 3);
// 这里创建了一个匿名函数(在第一个括号内),第二个括号用于调用该匿名函数,并传入参数。
“`

## 是否可以在JS中执行301重定向?
>- JS完全运行在客户端上。301是服务器作为响应发送的响应代码。因此,在JS中不可能执行301重定向。

## 解释JS中的事件冒泡和事件捕获
>- 事件捕获和冒泡: 在html DOM API中,有两种事件传播方法,它们决定了接收事件的顺序。两种方法是事件冒泡和事件捕获。第一个方法事件冒泡将事件指向其预期的目标,第二个方法称为事件捕获,其中事件向下到达元素。

>- 1. **事件捕获**
> 捕获过程很少被使用,但是当它被使用时,它被证明是非常有用的。这个过程也称为滴流模式。在这个过程中,事件首先由最外层的元素捕获,然后传播到最内部的元素。例如:
“`

// 从上面的示例中,假设单击事件发生在li元素中,在这种情况下,捕获事件将首先处理div,然后处理ul,最后命中目标元素li。
“`

>- 2. **事件冒泡**
> 冒泡的工作原理与冒泡类似,事件由最内部的元素处理,然后传播到外部元素。
“`

// 从上面的例子中,假设click事件确实发生在冒泡模型中的li元素中,该事件将首先由li处理,然后由ul处理,最后由div元素处理。
“`

## 如何将文件的所有导出作为一个对象?
>- import * as objectname from ‘./file.js’用于将所有导出的成员导入为对象。 可以使用对象的点(.)运算符来访问导出的变量或方法,如:
“`
objectname.member1;
objectname.member2;
objectname.memberfunc();
“`

## module.exports 和 exports 之间有什么区别?
> module和exports是Node.js给每个js文件内置的两个对象。可以通过console.log(module)和console.log(exports)打印出来。如果你在main.js中写入下面两行,然后运行 node main.js
“`
console.log(exports);//输出:{}
console.log(module);//输出:Module {…, exports: {}, …} (注:…代表省略了其他一些属性)
“`
>- 从打印咱们可以看出,module.exports和exports一开始都是一个空对象{},实际上,这两个对象指向同一块内存。这也就是说module.exports和exports是等价的(有个前提:不去改变它们指向的内存地址)。
>- 例如:exports.age = 18和module.export.age = 18,这两种写法是一致的(都相当于给最初的空对象{}添加了一个属性,通过require得到的就是{age: 18})。

## import和exports是什么?
> import和exports 帮助咱们编写模块化的JS代码。使用import和exports,咱们可以将代码分割成多个文件。import只允许获取文件的某些特定变量或方法。可以导入模块导出的方法或变量。
“`

//person.js

let name =’Sharad’, occupation=’developer’, age =26;

export { name, age};

//index.js

import name,age from ‘./person’;

console.log(name);
console.log(age);
“`

## JS中有哪些不同类型的弹出框可用
> 在JS中有三种类型的弹出框可用,分别是:
>- Alert
>- Confirm
>- Prompt

## 如何将JS日期转换为ISO标准
> toISOString() 方法用于将js日期转换为ISO标准.它使用ISO标准将js Date对象转换为字符串。如:
“`
var date = new Date();
var n = date.toISOString();
console.log(n);
// YYYY-MM-DDTHH:mm:ss.sssZ
“`

## 如何在JS中编码和解码 URL
> **encodeURI()** 是对整个URI进行转义,将URI中的非法字符转换为合法字符,所以对于一些在URI中有特殊意义的字符不会进行转义。
> **注意**: encodeURI()不会编码类似这样字符:/ ? : @ & = + $ #,如果需要编码这些字符,请使用encodeURIComponent()。用法:
“`
var uri = “my profile.php?name=sammer&occupation=pāntiNG”;
var encoded_uri = encodeURI(uri);
“`
> **decodeURI()** 函数用于解码js中的URL。它将编码的url字符串作为参数并返回已解码的字符串,用法:
“`
var uri = “my profile.php?name=sammer&occupation=pāntiNG”;
var encoded_uri = encodeURI(uri);
decodeURI(encoded_uri);
“`
> escape和encodeURI的作用相同,不过它们对于unicode编码为0xff之外字符的时候会有区别,escape是直接在字符的unicode编码前加上%u,而encodeURI首先会将字符转换为UTF-8的格式,再在每个字节前加上%。

## BOM和DOM的关系
> BOM全称Browser Object Model,即浏览器对象模型,主要处理浏览器窗口和框架。顶级对象是window,除了window还有history,navigator,location.
而window对象具有双重角色,它既是通过js访问浏览器窗口的一个接口,又是一个Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。

>DOM全称Document Object Model,即文档对象模型,是HTML和XML的应用程序接口(API),遵循W3C的标准,所有浏览器公共遵守的标准,顶级对象是document.主要定义了处理网页内容的方法和接口

>JS是通过访问BOM(Browser Object Model)对象来访问、控制、修改客户端(浏览器),由于BOM的window包含了document,window对象的属性和方法是直接可以使用而且被感知的,因此可以直接使用window对象的document属性,通过document属性就可以访问、检索、修改XHTML文档内容与结构。因为document对象又是DOM的根节点。

>可以说,BOM包含了DOM(对象),浏览器提供出来给予访问的是BOM对象,从BOM对象再访问到DOM对象,从而js可以操作浏览器以及浏览器读取到的文档。

## requestAnimationFrame介绍
> window.requestAnimationFrame()告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行.

>语法:window.requestAnimationFrame(callback);

>callback:下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。该回调函数会被传入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同,它表示requestAnimationFrame() 开始去执行回调函数的时刻。

> 一个long整数,请求ID,是回调列表中唯一的标识。是个非零值,没别的意义。你可以传这个值给window.cancelAnimationFrame()以取消回调函数。

> 回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。

> 在同一个帧中的多个回调函数,它们每一个都会接受到一个相同的时间戳,即使在计算上一个回调函数的工作负载期间已经消耗了一些时间。该时间戳是一个十进制数,单位毫秒,最小精度为1ms(1000μs)。

> 为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame()运行在后台标签页或者隐藏的