#
## 线程和进程是什么?举例说明
> 进程: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
“`
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()运行在后台标签页或者隐藏的
优点:
1. requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率;
setTimeout、setInterval它们的内在运行机制决定了 时间间隔参数 实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其它任务,那动画代码就要等前面的任务完成后再执行,并且如果时间间隔过短(小于16.7ms)会造成丢帧,所以就会导致动画可能不会按照预设的去执行,降低用户体验。
2. 在隐藏或不可见的元素中,将不会进行重新重绘或回流;
3. requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销
## 常见状态码
> 200,
201表示请求成功且服务器创建了新的资源
202表示服务器已经接受了请求,但还未处理
301永久重定向,302临时重定向
304表示自从上一次请求以来,页面的内容没有改变过
400表示请求报文中存在语法错误
403常见的跨域
404请求资源不存在,
500服务器错误
503表示服务器暂时无法处理请求
## 从输入URL到页面加载流程
> 1、DNS解析
>- 将域名转化为IP地址的工作。
如果浏览器有缓存,直接使用浏览器缓存,否则使用本级缓存,再没有就使用host;如果本地没有,就向dns域名服务器查询到对应的IP,由于dns的解析很耗时,当解析域名过多的时候,便会导致首屏加载变得过慢。
> 2、TCP连接
建立连接的三次握手
客户端:hello,你是服务端么?
服务端:hello,我是服务端,你是客户端么?
客户端:yes,我是客户端。
> 3、发送HTTP请求
> 4、服务器处理请求并返回HTTP报文
> 5、浏览器解析渲染页面
(1)处理HTML标记,构建DOM树;
(2)处理CSS标记,构建CSSOM树;
默认情况下,CSS是阻塞加载的,但它并不会阻塞DOM树的解析,它阻塞的是DOM的渲染,直至CSSOM树构建完成。因而为了避免看到长时间的白屏,我们尽可能早地提前加载CSS文件
(3)将DOM树和CSSOM树合并成渲染树;
(4)布局渲染树,计算各元素的尺寸、位置;
(5)绘制渲染树,绘制页面像素信息。
> 6、连接结束
断开连接的四次挥手
主动方:我已经关闭了向你那边的主动通道了
被动方:收到通道关闭的信息
被动方:那我也告诉你,我这边向你的主动通道也关闭了
主动方:最后收到数据,之后双方无法通信
## offsetX,clientX,pageX,screenX,layerX,x
>- offsetX:点击位置距离当前元素左边的距离(padding处算起始点)
>- clientX:点击位置距离浏览器窗口左边的距离
>- pageX:点击位置距离文档左边的距离
>- screenX:点击位置距离屏幕左边的距离
>- layerX:在点击元素设置相对定位或绝对定位时layerX == offsetX,(layerX是从border处算起始点)
>- x:xclientX(其他),xlayerX(IE)
## pc端浏览器兼容
1. 事件对象创建的兼容:
“`
var e=e || event
“`
2. 鼠标事件对象的属性
> Button属性 左键为0 右键为2 滚轮为1;
IE 左键为1 右键为2 滚轮为4;
3. 键盘事件对象的属性
> Keycode获取键盘的按键值 值为AscII码值
“`
e.keycode||e.which;
“`
4. 阻止事件冒泡兼容
“`
e.stopPropagation?e.stopPropagation:cancleBubble=true;
“`
5. 事件监听
> 绑定事件监听
“`
addEventListener();
attachEvent(); //ie
“`
> 取消事件监听
“`
removeEventListener();
detachEvent(); //ie
“`
6. 阻止浏览器的默认行为:
“`
e.preventDefault?e.preventDefault():e.returnValue=false;
“`
7. 事件的委托
“`
var e.target||e.srcElement; //后面ie
“`
8. 获取页面滚动的距离
“`
document.documentElement.scrollTop||document.body.ScrollTop; //后面ie
“`
9. 拖拽时候获取鼠标距离按下元素所在位置的偏移量:
“`
var disx = e.offsetX||e.layerX;
“`
10. 获取某元素的非行内样式:
“`
window.getComputedStyle?window.getComputedStyle(对象,false)[“属性”]:对象.currentStyle[“属性”]
“`
11. 获取可见区域宽高
– 在IE中:
“`
document.body.clientWidth //BODY对象宽度
document.body.clientHeight //BODY对象高度
document.documentElement.clientWidth //可见区域宽度
document.documentElement.clientHeight //可见区域高度
document.documentElement.scrollTop //窗口滚动条滚动高度
“`
– 在FireFox中:
“`
document.body.clientWidth //BODY对象宽度
document.body.clientHeight //BODY对象高度
document.documentElement.clientWidth //可见区域宽度
document.documentElement.clientHeight //可见区域高度
document.documentElement.scrollTop //窗口滚动条滚动高度
“`
– 在chrome中:
“`
document.body.clientWidth //BODY对象宽度
document.body.clientHeight //BODY对象高度
document.documentElement.clientWidth //可见区域宽度
document.documentElement.clientHeight //可见区域高度
document.body.scrollTop //窗口滚动条滚动高度
“`
– 在Opera中:
“`
document.body.clientWidth //可见区域宽度
document.body.clientHeight //可见区域高度
document.documentElement.clientWidth //页面对象宽度(即BODY对象宽度加上Margin宽)
document.documentElement.clientHeight //页面对象高度(即BODY对象高度加上Margin高)
window.scrollTo(0,0) //滚动到顶部
window.scrollTo(0,document.body.clientHeight) //滚动到尾部
“`
## 数组中的filter,every,flat的用法
> filter
1、创建新数组
2、不改变原数组
3、输出的是判断为true的数组元素形成的新数组
4、回调函数参数,item(数组元素)、index(序列)、arr(数组本身)
5、使用return操作输出,会循环数组每一项,并在回调函数中操作
“`
let arr = [1,2,3,4,5];
let newArr = arr.filter(function(item,index){
return item>3; //根据判断为true来遍历循环添加进新数组
})
console.log(newArr); //[4,5]
console.log(arr); //[1,2,3,4,5]
“`
> every
1、返回布尔值
2、不改变原数组
3、遍历每个元素判断是否符合条件函数
4、当所有元素满足条件时返回true,否为为false
“`
function isBigEnough(element,index,array) {
return (element >= 10);
}
let arr = [12,5,8,130,44]
let arr2 = [12,54,18,130,44]
let passed = arr.every(isBigEnough);
console.log(passed)
//passed is false
let passed2 = arr2.every(isBigEnough);
console.log(passed2)
//passed2 is true
“`
> flat
1、返回一个新的数组
2、数组扁平化处理
3、不传值情况下提取嵌套数组的结构深度,默认值为1
“`
const arr1 = [0,1,2,[3,4,[5,[6]]]];
console.log(arr1.flat());
//输出:[0,1,2,3,4,[5,[6]]]
console.log(arr1.flat(3));
//输出:[0,1,2,3,4,5,6]
“`
## es6新增特性
1. let、const:声明变量和常量
2. 模板字符串:增强版的字符串,用反引号标识“,嵌入变量只需要放在${}中
3. 箭头函数:ES6中函数定义不再使用关键字function(),而是利用了()=>来进行定义
4. 解构赋值:按照类型的不同有不同的方式提取值,赋值
5. Symbol:新增的基本数据类型,特点就是里面的值唯一,它接收一个可选的名字参数,该函数返回的symbol是唯一的
6. Set和Map数据结构,Set是类似于数组的数据集合,无序,插入删除速度快,元素不重复,查找速度快。Map是一个类似对象的数据结构,和对象不同的在于它的key可以是任意类型,但是对象只能使用string和symbol类型,Map的存储关联性更强
7. 展开运算符(…):可以将数组或对象里面的值展开,还可以将Set数据结构转换为数组
8. for…of循环:可以遍历数组对象以及Set和Map数据结构
9. class类:通过extends实现继承
10. promise、(async/await):都是用来解决异步编程的方案,可以通过编写形似同步的代码来处理异步流程, 提高代码的简洁性和可读性async用于申明一个function是异步的,而await用于等待一个异步方法执行完成.
11. proxy:代理对象,直接监听对象的变化,然后触发相应的逻辑
12. 新增了模块化(import/export)
## es6常用函数介绍
>- Array.from()
Array.from()将两类对象转为真正的数组:类似数组的对象和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)
“`
let arrayLike = {
‘0’:’a’,
‘1’:’b’,
‘2’:’c’,
length:3
};
let arr2 = Array.from(arrayLike);// [‘a’, ‘b’, ‘c’]
//Array.from()还可以接受第二个参数,用来对每个元素进行处理,将处理后的值放入返回的数组
Array.from([1, 2, 3],(x)=>x*x)
//[1,4,9]
“`
>- Array.of()
用于将一组值,转换为数组,没有参数的时候,返回一个空数组,当参数只有一个的时候,实际上是指定数组的长度,只有参数个数不少于2个时,Array()才会返回由参数组成的新数组
“`
Array() //[]
Array(3) //[, , ,]
Array(3,11,8)//[3, 11, 8]
“`
>- copyWithin()
“`
let arr=[1,2,3,3,3,3,4,5]
arr.copyWithin(0,5)
// [3,4,5,3,3,3,4,5]
console.log(arr)
/*
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为0。如果为负值,表示从末尾开始计算。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
*/
“`
>- find()、findIndex()
find()用于找出第一个符合条件的数组成员,参数是一个回调函数,接受三个参数依次为当前的值、当前的位置和原数组
“`
[1,5,10,15].find(function(value,index,arr){
return value > 9;
}) // 10
[1,5,10,15].findIndex(function(value,index,arr){
return value > 9;
}) // 2
//这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。
function thanAge(value){
return value > this.age;
}
let person = {name:’John’,age:20};
[10,12,26,15].find(thanAge,person); // 26
“`
>- fill()
使用给定值,填充一个数组,如果有给第二个和第三个参数,则用于指定填充的起始位置和结束位置,不包括结束位置,后面两个参数默认为0和length长度
“`
[‘a’,’b’,’c’].fill(7)
// [7,7,7]
new Array(3).fill(7)
// [7,7,7]
let a = [‘a’,’b’,’c’,’d’,’e’]
a.fill(7,1,4)
console.log(a)
// [“a”,7,7,7,”e”]
//注意,如果填充的类型为对象,则是浅拷贝
“`
>- entries(),keys(),values()
entries()是对键值对的遍历、keys()是对键名的遍历、values()是对键值的遍历
“`
for(let index of [‘a’,’b’].keys()){
console.log(index);
}
// 0
// 1
for(let elem of [‘a’,’b’].values()){
console.log(elem);
}
// ‘a’
// ‘b’
for(let [index, elem] of [‘a’,’b’].entries()){
console.log(index, elem);
}
// 0 “a”
“`
>- includes()
用于判断数组是否包含给定的值
“`
[1,2,3].includes(2) // true
[1,2,3].includes(4) // false
[1,2,NaN].includes(NaN) // true
//方法的第二个参数表示搜索的起始位置,默认为0,参数为负数则表示倒数的位置
[1,2,3].includes(3,3); // false
[1,2,3].includes(3,-1); // true
“`
>- flat(),flatMap()
flat()将数组扁平化处理,返回一个新数组,对原数据没有影响,flatMap()方法对原数组的每个成员执行一个函数相当于执行Array.prototype.map(),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组
“`
[1,2,[3,4]].flat()
//[1,2,3,4]
[1,2,[3,[4,5]]].flat()
// [1,2,3,[4,5]]
[1,2,[3,[4,5]]].flat(2)
// [1,2,3,4,5]
//flatMap()
let arr=[2,3,4]
let arr2=arr.flatMap((x) => [x * 2])
let arr3=arr.flatMap((x) => [x, x * 2])
console.log(arr2) // [4,6,8]
console.log(arr3) // [2,4,3,6,4,8]
//flatMap()方法还可以有第二个参数,用来绑定遍历函数里面的this
“`
>- super关键字
this关键字总是指向函数所在的当前对象,ES6又新增了关键字super,指向当前对象的原型对象
“`
const proto = {
msg: ‘hello’
};
const obj = {
msg: ‘world’,
find() {
return super.msg;
}
};
Object.setPrototypeOf(obj, proto); // 将proto设置为obj设置原型对象
obj.find() // “hello” 因为super指向原型对象,原型对象上msg关键字值为”hello”
“`
>- 遍历对象的属性方法
>for…in:循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)
Object.keys(obj):返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)的键名
Object.getOwnPropertyNames(obj):返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)的键名
Object.getOwnPropertySymbols(obj):返回一个数组,包含对象自身的所有Symbol属性的键名
Reflect.ownKeys(obj):返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是Symbol或字符串,也不管是否可枚举
>- Object.is()
严格判断两个值是否相等,与严格比较运算符(===)的行为基本一致,不同之处只有两个:一是+0不等于-0,二是NaN等于自身
“`
+0===-0 //true
NaN===NaN //false
Object.is(+0,-0) //false
Object.is(NaN,NaN) //true
“`
>- Object.assign()
Object.assign()方法用于对象的合并,将源对象source的所有可枚举属性,复制到目标对象target
Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象
“`
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target,source1,source2);
target //{a:1, b:2, c:3}
“`
>- Object.setPrototypeOf(),Object.getPrototypeOf()
“`
const proto = {
msg: ‘hello’
};
const obj = {
msg2: ‘world’,
};
Object.setPrototypeOf(obj, proto); // 将proto设置为obj设置原型对象
console.log(obj.msg,obj.msg2)
console.log(Object.getPrototypeOf(obj)) //{“msg”:”hello”}
“`
>- Object.keys()、Object.values()、Object.entries()
Object.keys()返回自身的(不含继承的)所有可遍历(enumerable)属性的键名的数组
Object.values()返回自身的(不含继承的)所有可遍历(enumerable)属性的键对应值的数组
Object.entries()返回一个对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对的数组
“`
var obj = { name: ‘张三’, age: 18 };
console.log(Object.keys(obj)) //[“name”,”age”]
console.log(Object.values(obj)) // [“张三”,18]
console.log(Object.entries(obj)) //[[“name”,”张三”],[“age”,18]]
“`
>- Object.fromEntries()
用于将一个键值对数组转为对象
“`
let ob=Object.fromEntries([
[‘name’,’张三’],
[‘age’,18]
])
console.log(ob)
//{name:”张三”,age:18}
“`
>- 函数新增length属性 (这点存疑)
length将返回没有指定默认值的参数个数
“`
(function(a){}).length //1
(function(a=5){}).length //0
(function(a,b,c=5){}).length //2
“`
## WeakSet和WeakMap
> WeakSet
WeakSet可以接受一个具有Iterable接口的对象作为参数,
在API中WeakSet与Set有两个区别:
>- 没有遍历操作的API
>- 没有size属性
>- WeackSet只能成员只能是引用类型,而不能是其他类型的值
>- WeakSet里面的引用只要在外部消失,它在WeakSet里面的引用就会自动消失
> WeakMap
WeakMap结构与Map结构类似,也是用于生成键值对的集合
在API中WeakMap与Map有两个区别:
>- 没有遍历操作的API
>- 没有clear清空方法
* WeakMap 可以使用set方法添加成员
* WeakMap 也可以接受一个数组,作为构造函数的参数
* 注意:WeakMap弱引用的只是键名,而不是键值。键值依然是正常引用
## 数组排序的方法
1. sort方法
> sort()方法用于对数组的元素进行排序,并返回数组。默认排序顺序是根据字符串Unicode码点
语法:array.sort(fun);参数fun可选。规定排序顺序。必须是函数。
比较函数应该具有两个参数a和b,其返回值如下:
若a小于b,在排序后的数组中a应该出现在b之前,则返回一个小于0的值。
若a等于b,则返回0。
若a大于b,则返回一个大于0的值。
简单点就是:比较函数两个参数a和b,返回a-b升序,返回b-a降序
注意:会修改原数组.
2. 冒泡排序法
> 每次比较相邻的两个数,如果后一个比前一个小,换位置
“`
let arr = [8, 5, 6, 99, 35, 48, 654, 666, 1];
function order(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
let temp = null;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
console.log(order(arr));
“`
3. 快速排序法
> 采用二分法,取出中间数,数组每次和中间数比较,小的放到左边,大的放到右边,而每个小的数组又可以继续看成更小的两个数组,一直递归下去,直到数组长度为0;重点是递归运用
“`
let arr = [8, 5, 6, 99, 35, 48, 654, 666, 1]
function order(arr) {
if(arr.length == 0) return [];
let CenterIndex = Math.floor(arr.length / 2);
let c = arr.splice(CenterIndex, 1);
let l = [];
let r = [];
for(let i = 0; i < arr.length; i++) {
if(arr[i] < c) {
l.push(arr[i])
} else {
r.push(arr[i])
}
}
return order(l).concat(c, order(r));
}
console.log(order(arr)); // [1, 5, 6, 8, 35, 48, 99, 654, 666]
```
## Promise.all()和Promise.race(),Promise.finally()
- Promise.all()
>– Promise.all()的作用是接收一组异步任务,然后并行执行任务,等所有任务执行完之后再执行回调
>- Promise.all()传入一组Promise数组,只有当所有的Promise 状态都成功才返回成功,只要有一个失败就返回失败的Promise 状态
>- Promise.finally()finally方法用于不管Promise的状态变为什么,都会执行它内部的函数
## for in和for of的区别
>- 两者都可以用于遍历数组,只不过for in遍历的是数组元素的索引(index),而for of遍历的是数组元素的值
>- for in可以遍历普通对象,获取的是对象的键名。for of不可以遍历普通对象
>- for in不可以遍历Set/Map for of可以遍历Set/Map
## typeof null的结果是什么,为什么?
>- 结果是object,因为对象在底层是用二进制表示的,在js中二进制前三位都为0的话会被判定为object类型,null为空,二进制表示全为0,所以就被判定为object了
## 面向对象的特征有哪些方面?
– 抽象:抽象是将一类对象的共同特征总结构建出来的过程,包括数据抽象和行为抽象两方面,抽象只关注对象有哪些行为或者属性,并不关注这些行为的细节是什么。
– 继承:继承是从已有类型得到继承信息创建新类的过程,提供继承的类叫父类、得到继承的类叫子类(派生类)
– 封装:通常认为封装是把数据的操作和操作数据的方法绑定起来,对数据的访问只能是已经定义的接口,可以说封装就是隐藏一切可以隐藏的东西,只向外界提供最简单的编程接口.
– 多态性:多态性是指允许不同子类型的对象对同一消息做出的不同的响应,简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情,实现多态需要做两件事:1、方法重写(子类继承父类并重写父类中的方法)2、对象造型(父类引用子类型对象,这样同样的引用调用的方法就会根据子类型的不同而表现出不同的行为)
## dom的作用和节点树
>- 将结构化文档(比如HTML和XML)解析成一系列的节点,再由这些节点组成一个树状结构(DOM Tree)。所有的节点和最终的树状结构,都有规范的对外接口。DOM不是JavaScript语法的一部分,但是DOM操作是JavaScript最常见的任务.
>- 节点树:一个文档的所有节点,按照所在的层级,可以抽象成一种树状结构。这种树状结构就是DOM树。它有一个顶层节点,下一层都是顶层节点的子节点,然后子节点又有自己的子节点,就这样层层衍生出一个金字塔结构,倒过来就像一棵树。
## 什么是事件,事件流
>- 事件,就是文档或浏览器窗口发生的一些特定的交互瞬间。可以使用监听器(或事件处理程序)来预定事件,以便事件发生时执行相应的代码。通俗的说,这种模型其实就是一个观察者模式。(事件是对象主题,而这一个个的监听器就是一个个观察者)
>- 事件流描述的就是从页面中接收事件的顺序。而IE和Netscape提出了完全相反的事件流概念。IE事件流是事件冒泡,而Netscape(网景)的事件流就是事件捕获。
## cookie,sessionStorage,localstorage
>- Cookie是小甜饼的意思。cookie是服务器提供的一种用于维护会话状态信息的数据,通过服务器发送到浏览器,浏览器保存在本地,当下一次有同源的请求时,将保存的cookie 值添加到请求头部,发送给服务端。这可以用来实现记录用户登录状态等功能。cookie一般可以存储4k大小的数据,并且只能够被同源的网页所共享访问。服务器端可以使用Set-Cookie的响应头部来配置cookie信息。一条cookie包括了5个属性值expires、domain、path、secure、HttpOnly。其中expires指定了cookie失效的时间,domain是域名、path是路径,domain和path一起限制了cookie能够被哪些url访问。secure规定了cookie只能在确保安全的情况下传输,HttpOnly规定了这个cookie只能被服务器访问,不能使用js脚本访问。以路径存储,上层路径不能访问下层的路径cookie,下层的路径cookie可以访问上层的路径cookie
在发生 xhr 的跨域请求的时候,即使是同源下的 cookie,也不会被自动添加到请求头部,除非显示地规定。
cookie最大特征就是可以在页面与服务器间互相传递,当发送或者接受数据时自动传递
>- localStorage是HTML5标准中新加入的技术,它并不是什么划时代的新东西。早在IE6时代,就有一个叫userData的东西用于本地存储,而当时考虑到浏览器兼容性,更通用的方案是使用Flash。而如今,localStorage被大多数浏览器所支持,如果你的网站需要支持IE6+,那以userData作为你方案是种不错的选择
>- sessionStorage与localStorage的接口类似,但保存数据的生命周期与localStorage不同。直译过来是“会话”。它只是可以将一部分数据在当前会话中保存下来,刷新页面数据依旧存在。但当页面关闭后,sessionStorage中的数据就会被清空。
>- 异同
>1. cookie用来保存登录信息,大小限制为4KB左右
>2. localStorage是Html5新增的,用于本地数据存储,保存的数据没有过期时间,一般浏览器大小限制在5MB
>3. sessionStorage接口方法和localStorage类似,但保存的数据的只会在当前会话中保存下来,页面关闭后会被清空。同页面不同窗口中数据不会共享
## class,extends,super
>- class定义一个类,其中有一个constructor方法,constructor方法中的this代表实例对象,constructor以外还有其他的方法,construct内定义的方法属性是实例对象自己的,constructor外的方法属性是所有实例对象共享的
>- class之间可以通过extends实现继承
>- super指代父类的实例,子类constructor中必须先调用super()方法,因为子类没有自己的this对象,是继承父类的this对象.
## js有哪些内置函数?
“`
Object
Array
Boolean
Number
String
Function
Date
RegExp
Error
“`
## JS变量按照存储方式有哪些类型?
– 1.值类型
– 2.引用类型(节省空间,公用内存块)
区别:值类型改变一个不会影响其他的,引用类型改变都改变,因为公用内存块
## 日期和math
– 日期
“`
console.log(Date.now()); // 获取当前毫秒数
var dt = new Date(); // 获取当前时间
console.log(dt.getTime()); // 当前时间的毫秒数
console.log(dt.getFullYear()); // 年
console.log(dt.getMonth()+1); // 月(0-11)
console.log(dt.getDate()); // 日(0-31)
console.log(dt.getHours()); // 时(0-23)
console.log(dt.getMinutes()); // 分(0-59)
console.log(dt.getSeconds()); // 秒(0-59)
“`
– Math
>- Math.random() 返回0~1之间的随机数
>- Math.abs(x) 返回数的绝对值
>- Math.ceil(x) 向上取整
>- Math.floor(x) 向下取整
## 写一个能遍历对象和数组的通用forEach函数
“`
function forEach(obj, fn) {
if (obj instanceof Array) {
obj.forEach(function (item, index) {
fn(index, item);
})
} else {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
fn(key, obj[key]);
}
}
}
}
var arr = [1, 2, 3, 4];
forEach(arr, function (index, item) {
console.log(index + ‘,’ + item);
});
var obj = {
x: 10,
y: 20
};
forEach(obj, function (index, item) {
console.log(index + ‘,’ + item);
});
“`
## 内部属性[[Class]]是什么?
“`
所有typeof返回值为”object”的对象(如数组)都包含一个内部属性[[Class]](我们可以把它看作一个内部的分类,而非传统的面向对象意义上的类)。这个属性无法直接访问,一般通过Object.prototype.toString(..)来查看。例如:
Object.prototype.toString.call( [1,2,3] );
// “[object Array]”
Object.prototype.toString.call( /regex-literal/i );
// “[object RegExp]”
“`
## js中整数的安全范围是多少?
> 安全整数指的是,在这个范围内的整数转化为二进制存储的时候不会出现精度丢失,能够被“安全”呈现的最大整数是2^53 – 1,即9007199254740991,在ES6中被定义为Number.MAX_SAFE_INTEGER。最小整数是-9007199254740991,在ES6中被定义Number.MIN_SAFE_INTEGER。
> 如果某次计算的结果得到了一个超过JavaScript数值范围的值,那么这个值会被自动转换为特殊的Infinity值。如果某次计算返回了正或负的Infinity值,那么该值将无法参与下一次的计算。判断一个数是不是有穷的,可以使用isFinite函数来判断。
## typeof NaN的结果是什么?
> NaN意指“不是一个数字”(not a number),NaN是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。
> typeof NaN; //”number”
> NaN是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即x ===x不成立)的值。而 NaN!= NaN为true。
## isNaN和Number.isNaN函数的区别?
> 函数isNaN接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回true,因此非数字值传入也会返回true,会影响NaN的判断。
> 函数Number.isNaN会首先判断传入参数是否为数字,如果是数字再继续判断是否为NaN,这种方法对于NaN的判断更为准确。
## Array构造函数只有一个参数值时的表现?
> Array构造函数只带一个数字参数的时候,该参数会被作为数组的预设长度(length),而非只充当数组中的一个元素。这样创建出来的只是一个空数组,只不过它的length属性被设置成了指定的值。
> 构造函数Array(..)不要求必须带new关键字。不带时,它会被自动补上。
## {}和[]的valueOf和toString的结果是什么?
> {}的valueOf结果为{},toString的结果为”[object Object]”
> []的valueOf结果为[],toString的结果为””
## 什么是假值对象?
> 浏览器在某些特定情况下,在常规JavaScript语法基础上自己创建了一些外来值,这些就是“假值对象”。假值对象看起来和普通对象并无二致(都有属性,等等),但将它们强制类型转换为布尔值时结果为false最常见的例子是document.all,它是一个类数组对象,包含了页面上的所有元素,由 DOM(而不是JavaScript引擎)提供给JavaScript程序使用。
## parseint和Number区别?
> 解析允许字符串(如parseInt())中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换(如Number())不允许出现非数字字符,否则会失败并返回NaN。
## +操作符什么时候用于字符串的拼接?
> 据ES5规范11.6.1节,如果+的其中一个操作数是字符串(或者通过以上步骤最终得到字符串),则执行字符串拼接,否则执行数字加法。
## ||和&&操作符的返回值
>- ||和&&首先会对第一个操作数执行条件判断,如果其不是布尔值就先进行强制类型转换,然后再执行条件判断。
>- 对于||来说,如果条件判断结果为true就返回第一个操作数的值,如果为false就返回第二个操作数的值。
>- &&则相反,如果条件判断结果为true就返回第二个操作数的值,如果为false就返回第一个操作数的值。
>- ||和&&返回它们其中一个操作数的值,而非条件判断的结果.
## Symbol值的强制类型转换
>- ES6允许从符号到字符串的显式强制类型转换,然而隐式强制类型转换会产生错误。
>- Symbol值不能够被强制类型转换为数字(显式和隐式都会产生错误),但可以被强制类型转换为布尔值(显式和隐式结果都是true)。
## ==操作符的强制类型转换规则?
>- (1)字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。
>- (2)其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。
>- (3)null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。
>- (4)对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。
>- (5)如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。
>- (6)如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true,否则,返回 false。
## 字符串转换为数字
>- (1)使用Number()方法,前提是所包含的字符串不包含不合法字符。
>- 使用parseInt()方法,parseInt()函数可解析一个字符串,并返回一个整数。还可以设置要解析的数字的基数。当基数的值为0,或没有设置该参数时,parseInt()会根据string来判断数字的基数。
>- 使用parseFloat()方法,该函数解析一个字符串参数并返回一个浮点数。
>- 使用+操作符的隐式转换。
## 数组的随机排序
>- 使用数组sort方法对数组元素随机排序,让Math.random()出来的数与0.5比较,如果大于就返回1交换位置,如果小于就返回-1,不交换位置。
“`
function randomSort(a,b){
return Math.random()>0.5?-1:1;
}
// 每个元素被派到新数组的位置不是随机的,原因是sort()方法是依次比较的。
“`
>- 随机从原数组抽取一个元素,加入到新数组
“`
function randomSort(arr){
let result = [];
while(arr.length>0){
let randomIndex = Math.floor(Math.random() * arr.length);
result.push(arr[randomIndex]);
arr.splice(randomIndex, 1);
}
return result;
}
“`
>- 随机交换数组内的元素(洗牌算法类似)
“`
function randomSort(arr){
var index,
randomIndex,
temp,
len = arr.length;
for (index = 0; index < len; index++) {
randomIndex = Math.floor(Math.random() * (len - index)) + index;
temp = arr[index];
arr[index] = arr[randomIndex];
arr[randomIndex] = temp;
}
return arr;
}
```
```
// es6
function randomSort(array) {
let length = array.length;
if (!Array.isArray(array) || length <= 1) return;
for (let index = 0; index < length - 1; index++) {
let randomIndex = Math.floor(Math.random() * (length - index)) + index;
[array[index], array[randomIndex]] = [array[randomIndex], array[index]];
}
return array;
}
```
## 通用的事件监听器函数
```
const EventUtils = {
// 视能力分别使用dom0||dom2||IE方式 来绑定事件
// 添加事件
addEvent: function(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
// 移除事件
removeEvent: function(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
// 获取事件目标
getTarget: function(event) {
return event.target || event.srcElement;
},
// 获取 event 对象的引用,取到事件的所有信息,确保随时能使用 event
getEvent: function(event) {
return event || window.event;
},
// 阻止事件(主要是事件冒泡,因为 IE 不支持事件捕获)
stopPropagation: function(event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
},
// 取消事件的默认行为
preventDefault: function(event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
};
```
## 使用Object.defineProperty()来进行数据劫持有什么缺点?
> 有一些对属性的操作,使用这种方法无法拦截,比如说通过下标方式修改数组数据或者给对象新增属性,vue内部通过重写函数解决了这个问题。在Vue3.0中已经不使用这种方式了,而是通过使用Proxy对对象进行代理,从而实现数据劫持。使用Proxy的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为这是ES6的语法。
## Javascript中,有一个函数,执行对象查找时,永远不会去查找原型,这个函数是?
>- hasOwnProperty
>- 所有继承了Object的对象都会继承到hasOwnProperty方法。这个方法可以用来检测一个对象是否含有特定的自身属性,和in运算符不同,该方法会忽略掉那些从原型链上继承到的属性。
## js延迟加载的方式有哪些?
> js的加载、解析和执行会阻塞页面的渲染过程,因此我们希望js脚本能够尽可能的延迟加载,提高页面的渲染速度。
>- 将js脚本放在文档的底部,来使js脚本尽可能的在最后来加载执行。
>- 第二种方式是给js脚本添加defer属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了defer属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
>- 第三种方式是给js脚本添加async属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行js脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个async属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
>- 第四种方式是动态创建DOM标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建script标签来引入js脚本。
## 浏览器的缓存机制?
>- 通过在一段时间内保留已接收到的web资源的一个副本,如果在资源的有效时间内,发起了对这个资源的再一次请求,那么浏览器会直接使用缓存的副本,而不是向服务器发起请求。使用web缓存可以有效地提高页面的打开速度,减少不必要的网络带宽的消耗。
>- web资源的缓存策略一般由服务器来指定,可以分为两种,分别是强缓存策略和协商缓存策略。
>- 使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。强缓存策略可以通过两种方式来设置,分别是http头信息中的Expires属性和Cache-Control属性。
>- 服务器通过在响应头中添加Expires属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。
>- Expires是http1.0中的方式,因为它的一些缺点,在http1.1中提出了一个新的头部属性就是Cache-Control属性,它提供了对资源的缓存的更精确的控制。它有很多不同的值,常用的比如我们可以通过设置max-age来指定资源能够被缓存的时间的大小,这是一个相对的时间,它会根据这个时间的大小和资源第一次请求时的时间来计算出资源过期的时间,因此相对于Expires来说,这种方式更加有效一些。
>- 常用的还有比如private,用来规定资源只能被客户端缓存,不能够代理服务器所缓存。还有如no-store,用来指定资源不能够被缓存,no-cache代表该资源能够被缓存,但是立即失效,每次都需要向服务器发起请求。
>- 一般来说只需要设置其中一种方式就可以实现强缓存策略,当两种方式一起使用时,Cache-Control的优先级要高于Expires。
>- 使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个304状态,让浏览器使用本地的缓存副本。如果资源发生了修改,则返回修改后的资源。协商缓存也可以通过两种方式来设置,分别是http头信息中的Etag和Last-Modified属性。
>- 服务器通过在响应头中添加Last-Modified属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添加一个If-Modified-Since的属性,属性值为上一次资源返回时的Last-Modified的值。当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回304状态,让客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种方法有一个缺点,就是Last-Modified标注的最后修改时间只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,那么文件已将改变了但是Last-Modified 却没有改变,这样会造成缓存命中的不准确。
>- 因为Last-Modified的这种可能发生的不准确性,http中提供了另外一种方式,那就是Etag属性。服务器在返回资源的时候,在头信息中添加了Etag属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时,浏览器会在请求头中添加一个If-None-Match属性,这个属性的值就是上次返回的资源的Etag的值。服务接收到请求后会根据这个值来和资源当前的Etag的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比Last-Modified的方式更加精确。
>- 当Last-Modified和Etag属性同时出现的时候,Etag的优先级更高。使用协商缓存的时候,服务器需要考虑负载平衡的问题,因此多个服务器上资源的Last-Modified应该保持一致,因为每个服务器上Etag的值都不一样,因此在考虑负载平衡时,最好不要设置Etag属性。
>- 强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。
## ajax解决浏览器缓存问题?
1.在ajax发送请求前加上setRequestHeader(“If-Modified-Since”,”0″)。
2.在ajax发送请求前加上setRequestHeader(“Cache-Control”,”no-cache”)。
3.在URL后面加上一个随机数:”fresh=”+Math.random();。
4.在URL后面加上时间戳:”nowtime=”+new Date().getTime();。
5.如果是使用jQuery,直接这样就可以了$.ajaxSetup({cache:false})。这样页面的所有ajax都会执行这条语句就是不需要保存缓存记录。
## js拖拽功能的实现
> 一个元素的拖拽过程,我们可以分为三个步骤,第一步是鼠标按下目标元素,第二步是鼠标保持按下的状态移动鼠标,第三步是鼠标抬起,拖拽过程结束。
> 这三步分别对应了三个事件,mousedown事件,mousemove事件和mouseup事件。只有在鼠标按下的状态移动鼠标我们才会执行拖拽事件,因此我们需要在mousedown事件中设置一个状态来标识鼠标已经按下,然后在mouseup事件中再取消这个状态。在mousedown事件中我们首先应该判断,目标元素是否为拖拽元素,如果是拖拽元素,我们就设置状态并且保存这个时候鼠标的位置。然后在mousemove事件中,我们通过判断鼠标现在的位置和以前位置的相对移动,来确定拖拽元素在移动中的坐标。
> 最后mouseup事件触发后,清除状态,结束拖拽事件。
## 谷歌浏览器v8引擎垃圾回收机制
> v8的垃圾回收机制基于分代回收机制,这个机制又基于世代假说,这个假说有两个特点,一是新生的对象容易早死,另一个是不死的对象会活得更久。基于这个假说,v8 引擎将内存分为了新生代和老生代。
> 新创建的对象或者只经历过一次的垃圾回收的对象被称为新生代。经历过多次垃圾回收的对象被称为老生代。
> 新生代被分为From和To两个空间,To一般是闲置的。当From空间满了的时候会执行Scavenge算法进行垃圾回收。当我们执行垃圾回收算法的时候应用逻辑将会停止,等垃圾回收结束后再继续执行。这个算法分为三步:
> 首先检查From空间的存活对象,如果对象存活则判断对象是否满足晋升到老生代的条件,如果满足条件则晋升到老生代。如果不满足条件则移动To空间。
> 如果对象不存活,则释放对象的空间。
> 最后将From空间和To空间角色进行交换。
> 新生代对象晋升到老生代有两个条件:
> 第一个是判断是对象否已经经过一次Scavenge回收。若经历过,则将对象从From空间复制到老生代中;若没有经历,则复制到To空间。
> 第二个是To空间的内存使用占比是否超过限制。当对象从From空间复制到To空间时,若To空间使用超过25%,则对象直接晋升到老生代中。设置25%的原因主要是因为算法结束后,两个空间结束后会交换位置,如果To空间的内存太小,会影响后续的内存分配。
> 老生代采用了标记清除法和标记压缩法。标记清除法首先会对内存中存活的对象进行标记,标记结束后清除掉那些没有标记的对象。由于标记清除后会造成很多的内存碎片,不便于后面的内存分配。所以了解决内存碎片的问题引入了标记压缩法。
> 由于在进行垃圾回收的时候会暂停应用的逻辑,对于新生代方法由于内存小,每次停顿的时间不会太长,但对于老生代来说每次垃圾回收的时间长,停顿会造成很大的影响。 为了解决这个问题 V8 引入了增量标记的方法,将一次停顿进行的过程分为了多步,每次执行完一小步就让运行逻辑执行一会,就这样交替运行。
## 实现一个页面操作不会整页刷新的网站,并且能在浏览器前进、后退时正确响应。给出你的技术实现方案?
> 通过使用pushState+ajax实现浏览器无刷新前进后退,当一次ajax调用成功后我们将一条state记录加入到history对象中。一条state记录包含了url、title 和content属性,在popstate事件中可以获取到这个state对象,我们可以使用content来传递数据。最后我们通过对window.onpopstate事件监听来响应浏览器的前进后退操作。
> 使用pushState来实现有两个问题,一个是打开首页时没有记录,我们可以使用replaceState来将首页的记录替换,另一个问题是当一个页面刷新的时候,仍然会向服务器端请求数据,因此如果请求的url需要后端的配合将其重定向到一个页面。
## 如何判断当前脚本运行在浏览器还是node环境中?
> this===window?’browser’:’node’;
> 通过判断Global对象是否为 window,如果不为window,当前脚本没有运行在浏览器中。
## 前端路由介绍?
> 前端路由就是把不同路由对应不同的内容或页面的任务交给前端来做,之前是通过服务端根据url的不同返回不同的页面实现的。
> 在单页面应用,大部分页面结构不变,只改变部分内容的使用
> 优点:用户体验好,不需要每次都从服务器全部获取,快速展现给用户
> 缺点:单页面无法记住之前滚动的位置,无法在前进,后退的时候记住滚动的位置
> 前端路由一共有两种实现方式,一种是通过hash的方式,一种是通过使用pushState的方式。
## 观察者模式和发布订阅模式?
> 发布订阅模式其实属于广义上的观察者模式
> 在观察者模式中,观察者需要直接订阅目标事件。在目标发出内容改变的事件后,直接接收事件并作出响应。
> 发布订阅模式中,发布者和订阅者之间多了一个调度中心。调度中心一方面从发布者接收事件,另一方面向订阅者发布事件,订阅者需要在调度中心中订阅事件。通过调度中心实现了发布者和订阅者关系的解耦。使用发布订阅者模式更利于我们代码的可维护性。
## 检查浏览器版本?
> 一种是检测window.navigator.userAgent的值,但这种方式很不可靠,因为userAgent可以被改写,并且早期的浏览器如ie,会通过伪装自己的userAgent的值为Mozilla
来躲过服务器的检测。
> 第二种方式是功能检测,根据每个浏览器独有的特性来进行判断,如ie下独有的ActiveX Object。
## 什么是polyfill?
> Polyfill指的是用于实现浏览器并不支持的原生API的代码。
> querySelectorAll是很多现代浏览器都支持的原生Web API,但是有些古老的浏览器并不支持,那么假设有人写了一段代码来实现这个功能使这些浏览器也支持了这个功能,那么这就可以成为一个Polyfill。
## Unicode和UTF-8之间的关系
> Unicode是一种字符集合,现在可容纳100多万个字符。每个字符对应一个不同的Unicode编码,它只规定了符号的二进制代码,却没有规定这个二进制代码在计算机中如何编码传输。
> UTF-8是一种对Unicode的编码方式,它是一种变长的编码方式,可以用1~4个字节来表示一个字符。
## 改变原数组的api
– 改变原数组的方法:
“`
shift()
unshift()
pop()
push()
reverse()
sort()
splice()
“`
– 不改变原数组的方法:
“`
concat()
every()
filter()
forEach()
indexOf()
join()
lastIndexOf()
map()
some()
every()
slice()
reduce()
reduceRight()
flat()
flatMap()
find()
“`
## for循环与forEach的区别?
> for循环可以使用break跳出循环,但forEach不能。
> for循环可以控制循环起点(i初始化的数字决定循环的起点),forEach只能默认从索引0开始。
> for循环过程中支持修改索引(修改i),但forEach做不到(底层控制index自增,无法左右它)。
暂无评论内容