原型链的理解
- 看一个实例
var animal = function() {};
var dog = function() {};
animal.prototype.price = 20;
animal.price = 1000;
dog.prototype = animal;
var cat = new animal();
var tidy = new dog();
// 下面两行分别输出什么?
console.log(cat.price);
console.log(tidy.price);
-
原型和原型链首先要知道几个概念:
- 在js里,继承机制是原型继承。继承的起点是 对象的原型(Object prototype)。
- 一切皆为对象,只要是对象,就会有 proto属性,该属性存储了指向其构造的指针。
- Object prototype也是对象,其 proto指向null。
- 对象分为两种:函数对象和普通对象,只有函数对象拥有『原型』对象(prototype)。
- prototype的本质是普通对象。
- Function prototype比较特殊,是没有prototype的函数对象。
- new操作得到的对象是普通对象。
当调取一个对象的属性时,会先在本身查找,若无,就根据 proto找到构造原型,若无,继续往上找。最后会到达顶层Object prototype,它的 proto指向null,均无结果则返回undefined,结束。
-
由 proto 串起的路径就是『原型链』。
image cat调用price
① 查看本身,没有price,根据 proto找到 animal prototype对象。
② 在animal prototype对象中找到了price,所以 cat.price 结果为20。
③ 如果无 animal.prototype=20,则会根据animal prototype对象的 proto 找到Object prototype,Object prototype的proto指向null,若仍没有找到price,结果则为undefined。tidy调用price
① 查看本身,没有price,根据 proto找到dog prototype。
② dog prototype = animal,这里可以这样理解:prototype本质也是一个对象,因此可以重新赋一个对象,无论是函数对象还是普通对象。事实上,每个 prototype 会有一个预定义的constructor属性用来引用它的函数对象。
③ 在animal中找到了price, 所以 tidy.price 结果为1000。
④ 如果无 animal.price = 1000,则会根据animal的proto找到下一个对象,最终都没有找到 price,结果就为undefined。-
深入
- 创建一个对象的方式
- {}、new Object()
- 构造函数
- Object.create()
此方法将新对象的proto更改并指向create的入参对象。
- instance of VS constructor
- instance of 原理:检查左边对象与右边对象是否在同一条原型链上。
- constructor原理:取对象的proto属性指向的prototype对象上的constructor字段。
- 创建一个对象的方式
cat instanceof animal === true
cat.__proto__ === animal.prototype
animal.prototype instanceof Object === true
animal.prototype.__proto__ === Object.prototype
cat instanceof Object === true
// but,
cat.constructor === animal // true
cat.constructor === Object // false
- new运算符的原理
- 创建一个空对象,它的proto等于构造函数的原型对象(可以用Object.create()完成)
- 构造函数以第1步创建的对象做为上下文,是否会返回一个对象
- 若第2步返回了对象,则使用该对象作为新实例,否则用第1步创建的对象作为新实例
var myNew = function (func) {
var o = Object.create(func.prototype)
var i = func.call(o)
return typeof i === 'object' ? i : o
}
-
继承
- 类的声明
- function
- class
- 类的声明
生成实例:new
-
继承的几种方式:
- 借助构造函数,父类的作用域指向子类
- Parent.call(this) // this是Child类的上下文
- 缺点:不能继承原型链属性
- 借助原型链
- Child.prototype = new Parent()
- 缺点:子类所有实例共享原型对象;子类实例的constructor为Parent
-
组合方式
- Parent.call(this) // this是Child类的上下文
- Child.prototype = Object.create(Parent.prototype)
- Child.prototype.constructor = Child
.bind()、.live()、.delegate()和.on()之间的区别
- bind
.bind()方法将会把事件处理函数连接到所有匹配的标签。这种方式并不好。这样做的话,它不仅在所有匹配的元素中隐含地迭代附加事件处理函数,而且这些操作非常浪费(多余),因为这些相同的事件处理函数是被一遍一遍的重复的添加到所有匹配的标签上。- 优点:
- 适用于各种浏览器
- 连接事件处理函数非常方便快捷
- 可以使用 .click() , .hover()等简写方法来更方面地连接事件处理函数
- 对于一个简单的ID选择器,使用.bind() 方法不仅可以很快地连接事件处理函数,而且当事件被触发时, 事件处理函数几乎是马上就被调用了
- 缺点:
- 这样方法会将所有的事件处理函数附加到所有匹配的元素
- 不可以动态地匹配相同选择器的元素
- 当操作大量匹配的元素时会有性能方面的问题
- 附加操作是在前期完成的,这可能导致页面加载时存在性能问题
- 优点:
- Live
.live()方法使用了事件委托的概念来实施其所谓的“魔法”。你调用live()方法的方式就像是调用bind()方法那样方便。然而在这表面之下, .live()方法与前者的实现方式大不相同。 .live()方法将与事件处理函数关联的选择器和事件信息一起附加到文档的根级元素(即document)。通过将事件信息注册到document上,这个事件处理函数将允许所有冒泡到document的事件调用它(例如委托型、传播型事件)。一旦有一个事件冒泡到document元素上,Jquery会根据选择器或者事件的元数据来决定哪一个事件处理函数应该被调用,如果这个事件处理函数存在的话。这个额外的工作将会在用户交互时对性能方面造成一定的影响,但是初始化注册事件的过程相当地快。
/* 方法将与事件处理函数关联的选择器和事件信息一起附加到文档的根级元素(即document) ( "#members li a" & "click" ) */
$( "#members li a" ).live( "click", function( e ) {} );
.bind()这个例子与上面bind()方法的例子对比的话有一个优点在于它仅仅把事件处理函数附加到document元素一次,而不是很多次。这样不仅更快,而且还减少了性能的浪费。然而,使用这个方法也会带来很多问题,下面将一一列出。
-
优点:
- 所有的事件处理函数都只会被注册一次,而不是像bind()那样进行多次注册
- 将bind()方法升级到live()方法非常方便,你仅需要将"bind"替代为"live"就可以了
- 那些被动态添加到DOM的元素也将被神奇的匹配到,因为真实的事件信息是被注册到document元素上的
你可以在文档加载完之前连接事件处理函数,这样可以帮助你更好地利用你可能没有用的时间
-
缺点:
- 这个方法在Jquery 1.7以后的版本被弃用了,你应该在你的代码里逐步放弃使用它
- 使用这个方法时链式操作没有得到正确的支持,可能会出现某些错误
- 所做的匹配操作基本上没用因为它只用于在document元素上注册事件处理函数
- 使用 event.stopPropogation() 方法将会没用,因为事件总是已经被委托到了document元素上
- 因为所有的选择器或者事件信息都被附加到document元素上了,所以 一旦有一个事件要调用某个事件处理函数,Jquery会在一大堆储存的元数据中使用matchesSelector方法来决定哪一个事件处理函数将会被调用,如果这个函数有的话。
- 因为你所连接的事件总是被委托到document上,所如果你的DOM的层级很深的话,这会导致一定的性能问题
Delegate
.delegate()方法与live()方式实现方式相类似,它不是将选择器或者事件信息附加到document,而是让你指定附加的元素。就像是live()方法一样,这个方法使用事件委托来正确地工作。
如果你跳过了前面关于 .live() 方法的介绍,你可能要回去重新看看它,因为这里涉及到之前我所阐述的一些内部逻辑
/* .delegate() 方法会将选择器和事件信息 ( "li a" & "click" ) 附加到你指定的元素上 ( "#members" )。
*/
$( "#members" ).delegate( "li a", "click", function( e ) {} );
.delegate()方法十分强大。在上面这个例子中,与事件处理函数关联的选择器和事件信息将会被附加到( #members" )这个元素上。这样做比使用live()高效多了,因为live()方法总是将与事件处理函数关联的选择器和事件信息附加到document元素上。另外,使用.delegate()方法解决许多其他问题。请参阅下方列出的详细信息。
-
优点:
- 你可以选择将选择器或者事件信息附加到指定的元素。
- 匹配操作实际上在前面并没有执行,而是用来注册到指定的元素。
- 链式操作可以得到正确的支持
- Jquery仍然需要迭代这些选择器或者事件信息来匹配元素,不过因为你可以选择哪一个元素作为根元素,所以筛选的量会大幅减少
因为这项技术使用了事件委托机制,它可以匹配到被动态地添加到DOM的元素 - 你可以在文档加载完之前连接事件处理函数
-
缺点:
- 从.bind()方法不可以直接升级到.delegate()方法
- Jquery仍然需要使用marchesSelector方法在附加到指定根元素的选择器或者事件信息中筛选决定哪一个事件处理函数会被调用。然而,附加到指定根元素的元数据会比使用live()方法的时候要小得多。
当操作大量匹配的元素时会有性能方面的问题 - 附加操作是在前期完成的,这可能导致页面加载时存在性能问题
On
你知道吗,在Jquery 1.7版本中.bind() , .live() 和.delegate()方法只需要使用.on()方法一种方式来调用它们。当然.unbind() , .die() 和.undelegate()方法也一样。一下代码片段是从Jquery 1.7版本的源码中截取出来的
bind: function( types, data, fn ) {
return this.on( types, null, data, fn );
},
unbind: function( types, fn ) {
return this.off( types, null, fn );
},
live: function( types, data, fn ) {
jQuery( this.context ).on( types, this.selector, data, fn );
return this;
},
die: function( types, fn ) {
jQuery( this.context ).off( types, this.selector || "**", fn );
return this;
},
delegate: function( selector, types, data, fn ) {
return this.on( types, selector, data, fn );
},
undelegate: function( selector, types, fn ) {
return arguments.length == 1 ?
this.off( selector, "**" ) :
this.off( types, selector, fn );
}
//考虑到这一点,使用.on()方法看起来像以下方式一样...
/* Jquery的 .bind() , .live() 和 .delegate() 方法只需要使用`.on()`方法一种方式来调用它们 */
// Bind
$( "#members li a" ).on( "click", function( e ) {} );
$( "#members li a" ).bind( "click", function( e ) {} );
// Live
$( document ).on( "click", "#members li a", function( e ) {} );
$( "#members li a" ).live( "click", function( e ) {} );
// Delegate
$( "#members" ).on( "click", "li a", function( e ) {} );
$( "#members" ).delegate( "li a", "click", function( e ) {} );
你可能注意到了,我如何使用.on()方法决定了它如何调用其他方法。你可以认为.on()方法被具有不同签名的方法”重载“了,而这些方法实现了不同的事件绑定的连接方式。 .on()方法的出现为API带来了很多方面的一致性,并希望让事情变得不那么混乱。
- 优点:
- 使各种事件绑定方法一致。
- 因为在Jquery源码中.bind() , .live() 和.delegate()方法实际上是调用了此方法,因此简化了jQuery代码库并删除了一级重定向。
- 这种方式仍然提供了使用.delegate()方法的优点,并且仍然提供对.bind()方法的支持,如果你需要的话。
- 缺点:
- 给人带来了一些疑惑,因为方法的实际执行方式将根据你如何调用方法而改变。
- 总结
使用.bind()方法非常浪费性能因为它把同一个事件处理函数附加到了每一个匹配的元素上
你应该停止使用.live()方法因为它被弃用了同时也会带来很多问题
使用.delegate()方法会给你带来很多好处当你需要解决一些性能上的问题和对动态添加的元素作出处理
新的.on()方法其实就是模拟.bind() , .live() 和.delegate()实现的语法糖,具体取决于你如何调用它
新的方向是使用新的.on()方法。先熟悉语法,并开始在你的所有的Jquery 1.7版本以上的项目使用它吧!
状态码说明
- 1**
信息,服务器收到请求,需要请求者继续执行操作 - 2**
成功,操作被成功接收并处理 - 3**
重定向,需要进一步的操作以完成请求 - 4**
客户端错误,请求包含语法错误或无法完成请求 - 5**
服务器错误,服务器在处理请求的过程中发生了错误 - 100
继续 - 101
切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议 - 200
OK 、请求成功。一般用于GET与POST请求 - 201
已创建。成功请求并创建了新的资源 - 202
已接受。已经接受请求,但未处理完成 - 203
非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本 - 204
无内容。服务器成功处理,但未返回内容。在未更新网页的情况下,可确保浏览器继续显示当前文档 - 205
重置内容。服务器处理成功,用户终端(例如:浏览器)应重置文档视图。可通过此返回码清除浏览器的表单域 - 206
部分内容。服务器成功处理了部分GET请求 - 300
多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择 - 301
永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替 - 302
临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI - 303
查看其它地址。与301类似。使用GET和POST请求查看 - 304
未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 - 305
使用代理。所请求的资源必须通过代理访问 - 306
已经被废弃的HTTP状态码 - 307
临时重定向。与302类似。使用GET请求重定向 - 400
客户端请求的语法错误,服务器无法理解 - 401
请求要求用户的身份认证 - 402
保留,将来使用 - 403
服务器理解请求客户端的请求,但是拒绝执行此请求 - 404
服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 - 405
客户端请求中的方法被禁止 - 406
服务器无法根据客户端请求的内容特性完成请求 - 407
请求要求代理的身份认证,与401类似,但请求者应当使用代理进行授权 | - 408
服务器等待客户端发送的请求时间过长,超时 - 409
服务器完成客户端的 PUT 请求时可能返回此代码,服务器处理请求时发生了冲突 - 410
客户端请求的资源已经不存在。410不同于404,如果资源以前有现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置 - 411
服务器无法处理客户端发送的不带Content-Length的请求信息 - 412
客户端请求信息的先决条件错误 - 413
由于请求的实体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,服务器可能会关闭连接。如果只是服务器暂时无法处理,则会包含一个Retry-After的响应信息 - 414
请求的URI过长(URI通常为网址),服务器无法处理 - 415
服务器无法处理请求附带的媒体格式 - 416
客户端请求的范围无效 - 417
服务器无法满足Expect的请求头信息 - 500
服务器内部错误,无法完成请求 - 501
服务器不支持请求的功能,无法完成请求 - 502
作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应 - 503
由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中 - 504
充当网关或代理的服务器,未及时从远端服务器获取请求 - 505
服务器不支持请求的HTTP协议的版本,无法完成处理
promise、generator、async/await怎么使用,有什么区别
-
异步编程的方法有下面四种
- 回调函数
- 事件监听
- 发布/订阅
- Promise对象
- async/await
回调函数
所谓回调函数(callback),就是把任务分成两步完成,第二步单独写在一个函数里面,等到重新执行这个任务时,就直接调用这个函数。
// 例如Node.js中读取文件
fs.readFile('a,txt', (err,data) = >{
if(err) throw err;
console.log(data);
})
上面代码中readFile的第二个参数就是回调函数,等到读取完a.txt文件时,这个函数才会执行。
- Promise
使用回调函数本身没有问题,但有“回调地狱”的问题。
假定我们有一个需求,读取完A文件之后读取B文件,再读取C文件,代码如下
fs.readFile(fileA, (err, data) => {
fs.readFile(fileB, (err, data) => {
fs.readFile(fileC, (err,data)=>{
//do something
})
});
});
//这时候Promise出现了!它不是新的功能,而是一种新的写法,用来解决“回调地狱”的问题。
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function A(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return A(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return A(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return A(n);
}
上面代码中有4个函数,A()返回一个Promise对象,接收参数n,n秒后执行resolve(n+200)。step1、 step2、step3对应三个步骤
现在用Promise实现这三个步骤:
function doIt() {
console.time('do it now')
const time1 = 300;
step1(time1)
.then( time2 =>step2(time2))
.then( time3 => step3(time3))
.then( result => {
console.log(`result is ${result}`)
});
}
doIt();
// 输出结果如下
step1 with 300
step2 with 500
step3 with 700
result is 900
result是step3()的参数700+200 = 900。
可见,Promise的写法只是回调函数的改进,用then()方法免去了嵌套,更为直观。
但这样写绝不是最好的,代码变得十分冗余,一堆的then。
所以,最优秀的解决方案是什么呢?
开头暴露了,就是async/await
讲async前我们先讲讲协程与Generator
- 协程
协程(coroutine),意思是多个线程相互协作,完成异步任务。
它的运行流程如下
协程A开始执行
协程A执行到一半,暂停执行,执行的权利转交给协程B。
一段时间后B交还执行权
协程A重得执行权,继续执行
上面的协程A就是一个异步任务,因为在执行过程中执行权被B抢了,被迫分成两步完成。
读取文件的协程代码如下:
function task() {
// 其他代码
var f = yield readFile('a.txt')
// 其他代码
}
task()函数就是一个协程,函数内部有个新单词yield,yield中文意思为退让,
顾名思义,它表示执行到此处,task协程该交出它的执行权了。也可以把yield命令理解为异步两个阶段的分界线。
协程遇到yield命令就会暂停,把执行权交给其他协程,等到执行权返回继续往后执行。最大的优点就是代码写法和同步操作几乎没有差别,只是多了yield命令。
这也是异步编程追求的,让它更像同步编程
- Generator函数
Generator是协程在ES6的实现,最大的特点就是可以交出函数的执行权,懂得退让。
function* gen(x) {
var y = yield x +2;
return y;
}
var g = gen(1);
console.log( g.next()) // { value: 3, done: false }
console.log( g.next()) // { value: undefined, done: true }
上面代码中,函数多了*号,用来表示这是一个Generator函数,和普通函数不一样,不同之处在于执行它不会返回结果,
返回的是指针对象g,这个指针g有个next方法,调用它会执行异步任务的第一步。
对象中有两个值,value和done,value 属性是 yield 语句后面表达式的值,表示当前阶段的值,done表示是否Generator函数是否执行完毕。
下面看看Generator函数如何执行一个真实的异步任务
var fetch = require('node-fetch');
function gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}
var g = gen();
var result = g.next();
result.value.then( data => return data.json)
.then (data => g.next(data))
上面代码中,首先执行Generator函数,得到对象g,调用next方法,此时
result ={ value: Promise {
因为fetch返回的是一个Promise对象,(即value是一个Promise对象)所以要用then才能调用下一个next方法。
虽然Generator将异步操作表示得很简洁,但是管理麻烦,何时执行第一阶段,又何时执行第二阶段?
是的,这时候到Async/await出现了!
- Async/await
从回调函数,到Promise对象,再到Generator函数,JavaScript异步编程解决方案历程可谓辛酸,终于到了Async/await。很多人认为它是异步操作的最终解决方案(谢天谢地,这下不用再学新的解决方案了吧)
其实async函数就是Generator函数的语法糖,例如下面两个代码:
var gen = function* (){
var f1 = yield readFile('./a.txt');
var f2 = yield readFile('./b.txt');
console.log(f1.toString());
console.log(f2.toString());
};
var asyncReadFile = async function (){
var f1 = await readFile('./a.txt');
var f2 = await readFile('./b.txt');
console.log(f1.toString());
console.log(f2.toString());
};
上面的为Generator函数读取两个文件,下面为async/await读取,比较可发现,两个函数其实是一样的,async不过是把Generator函数的*号换成async,yield换成await。
1.async函数用法
上面说了async不过是Generator函数的语法糖,那为什么要取这个名字呢?自然是有理由的。
async是“异步”,而await是async wait的简写,即异步等待。所以应该很好理解async用于声明一个function是异步的,await用于等待一个异步方法执行完成
下面来看一个例子理解async命令的作用
async function test() {
return "async 有什么用?";
}
const result = test();
console.log(result)
输出:
Promise { 'async 有什么用?' }
可以看到,输出的是一个Promise对象!
所以,async函数返回的是一个Promise对象,如果直接return 一个直接量,async会把这个直接量通过PromIse.resolve()封装成Promise对象
注意点
一般来说,都认为await是在等待一个async函数完成,确切的说等待的是一个表示式,这个表达式的计算结果是Promise对象或者是其他值(没有限定是什么)
即await后面不仅可以接Promise,还可以接普通函数或者直接量。
同时,我们可以把async理解为一个运算符,用于组成表达式,表达式的结果取决于它等到的东西
等到非Promise对象 表达式结果为它等到的东西
等到Promise对象 await就会阻塞后面的代码,等待Promise对象resolve,取得resolve的值,作为表达式的结果
还是那个业务,分多个步骤完成,每个步骤依赖于上一个步骤的结果,用setTimeout模拟异步操作。
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
async实现方法
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
输出结果和上面用Promise实现是一样的,但这个代码结构看起来清晰得多,几乎跟同步写法一样。
- async函数的优点
(1)内置执行器
Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
(2) 语义化更好
async 和 await,比起星号和 yield,语义更清楚了。async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
(3)更广的适用性
yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作
js中的继承?
- 原型链继承 JavaScript实现继承的基本思想:通过原型将一个引用类型继承另一个引用类型的属性和方法。
- 借用构造函数继承(伪造对象或经典继承) JavaScript实现继承的基本思想:在子类构造函数内部调用超类型构造函数。 通过使用apply()和call()方法可以在新创建的子类对象上执行构造函数。
- 组合继承(原型+借用构造)(伪经典继承) JavaScript实现继承的基本思想:将原型链和借用构造函数的技术组合在一块,从而发挥两者之长的一种继承模式。
- 将原型链和借用构造函数的技术组合到一起,从而取长补短发挥两者长处的一种继承模式。
- 原型式继承 JavaScript实现继承的基本思想:借助原型可以基于已有的对象创建新对象,同时还不必须因此创建自定义的类型。
- 寄生式继承 JavaScript实现继承的基本思想:创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真正是它做了所有工作一样返回对象。
- 寄生式继承是原型式继承的加强版。
- 寄生组合式继承 JavaScript实现继承的基本思想:通过借用函数来继承属性,通过原型链的混成形式来继承方法。
常用的js数组操作方法有哪些?
Array.shift()
删除并返回第一个元素 作用:从数组中删除第一个元素(即下标为0的元素),并返回该元素。
注意:
1、删除元素之后,数组的长度-1。
2、如果数组是空的,那么 shift() 方法将不进行任何操作,返回 undefined 值。Array.pop()
删除并返回最后一个元素 作用:从数组中删除最后一个元素(即下标为length-1的元素),并返回该元素。
注意:
1、删除元素之后,数组的长度-1。
2、如果数组是空的,那么 shift() 方法将不进行任何操作,返回 undefined 值。Array.push(param1[,param2,...paramN])
尾部添加元素 作用:在数组的尾部添加一个元素,并返回新数组的长度。
注意
1、它是直接修改该数组,而不是重新创建一个数组。
2、它和pop是一对相反的先进后出的栈功能方法。
3、它可以同时给一个数组添加多个元素。Array.unshift(newElement1[,newElement2,...newElementN]
头部添加元素 作用:在数组的头部添加一个或多个元素,并返回新数组的长度。
注意:
1、它是直接修改该数组,而不是重新创建一个数组。
2、IE浏览器不支持该方法。Array.join([separator])
转换成字符串 作用:把数组的所有元素放入到一个字符串中。
注意:
1、参数separator表示字符串中元素的分隔符,可以为空,默认为半角逗号。
2、该方法并不修改数组。Array.contact(array1[,array2,...arrayN])
连接数组 作用:将两个或两个以上的数组连接成一个数组,并返回连接后的数组。
注意:
1、该方法并不会改变现有的数组,而是返回被连接的多个数组的一个副本。
2、如果多个数组里有值相同的元素,那也不会重复出现,而不会把重复的元素过滤掉。Array.reverse()
反转数组 作用:把数组的所有元素顺序反转。
注意:
1、该方法会直接修改数组,而不会创建新的数组。Array.slice(start[, end])
截取数组 作用:截取数组中指定位置的元素,并返回一个新的子数组。
注意:
1、该方法并不会改变现有的数组,而是原数组的一个子数组。
2、参数start是必选,表示开始下标,如果start为负数,表示从末尾开始,-1表示最后一个元素,依次类推。
3、end是可选表示结束下标,如果没有指定,表示到结尾元素。Array.splice()
删除指定元素 作用:从数组指定位置删除指定数量的元素,并返回被删除的元素。
注意:
1、该方法会直接修改数组。
2、splice() 方法与 slice() 方法的作用是不同的,splice() 方法会直接对数组进行修改,而slice只是截取原数组的一部分后返回一个子数组,并不会修改原数组。Array.toString()
转换成字符串 作用:数组转换为字符串,并返回该字符串。
注意:
1、该方法和不带参数的join()方法效果一样。
js数组去重
-
使用es6 set方法 [...new Set(arr)]
let arr = [1,2,3,4,3,2,3,4,6,7,6]; let unique = (arr)=> [...new Set(arr)]; unique(arr); //[1, 2, 3, 4, 6, 7]
利用新数组indexOf查找 indexOf() 方法可返回某个指定的元素在数组中首次出现的位置。如果没有就返回-1。
-
for 双重循环 通过判断第二层循环,去重的数组中是否含有该元素,如果有就退出第二层循环,如果没有j==result.length就相等,然后把对应的元素添加到最后的数组里面。
let arr = [1,2,3,4,3,2,3,4,6,7,6]; let result = []; for(var i = 0 ; i< arr.length; i++) { for(var j = 0 ; j < result.length ; j++) { if( arr[i] === result[j]){ break; }; }; if(j == result.length){ result.push(arr[i]); }; }; console.log(result);
-
利用for嵌套for,然后splice去重
functionunique(arr){ for(vari=0; i
-
利用filter
let arr = [1,2,3,4,3,2,3,4,6,7,6]; let unique = (arr) => { return arr.filter((item,index) => { return arr.indexOf(item) === index; }) }; unique(arr); let arr = [1,2,3,4,3,2,3,4,6,7,6]; let unique = (arr) => { return arr.filter((item,index) => { return arr.indexOf(item) === index; }) }; unique(arr);
-
利用Map数据结构去重
let arr = [1,2,3,4,3,2,3,4,6,7,6]; let unique = (arr)=> { let seen = new Map(); return arr.filter((item) => { return !seen.has(item) && seen.set(item,1); }); }; unique(arr);
数组中的forEach和map的区别?
- 相同点
- 都是循环遍历数组中的每一项
- forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项),index(索引值),arr(原数组)
- 匿名函数中的this都是指向window 只能遍历数组 都不会改变原数组
- 区别
- map方法返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。
- map方法不会对空数组进行检测,map方法不会改变原始数组。
- 浏览器支持:chrome、Safari1.5+、opera都支持,IE9+, 若arr为空数组,则map方法返回的也是一个空数组。
- forEach方法用来调用数组的每个元素,将元素传给回调函数
- forEach对于空数组是不会调用回调函数的。 无论arr是不是空数组,forEach返回的都是undefined。这个方法只是将数组中的每一项作为callback的参数执行一次。
js事件循环 event loop?
- 主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
Math.min()和Math.max()大小比较?
- Math.max() Math.min() Math.min()如果没有参数,则返回 Infinity。
- Infinity 是javascript 中全局对象的一个属性,在浏览器环境中就是 window 对象的一个属性,表示无穷大。
- Math.max()没有传递参数时返回的是 -Infinity。 因此 Math.min() 要比 Math.max() 大。
怎么实现对象的深浅拷贝?
- 浅拷贝很容易,只需要使用赋值运算符(=)即可
文件上传如何做断点续传?
- 文件断点续传是HTML5引入的新特性,HTML5的FILE api,有一个slice方法,可以将BLOB对象进行分割。前端通过FileList对象获取到相应的文件,按照指定的分割方式将大文件分段,然后一段一段地传给后端,后端再按顺序一段段将文件进行拼接。
- 断点续传原理,目前比较常用的断点续传的方法有两种,
- 一种是通过websocket接口进行文件上传,
- 另一种是通过ajax,两种方法各有千秋,虽然websocket听起来比较高端些,但是除了用了不同的协议外其他的算法基本上都是很相似的,并且服务端要开启ws接口,这里用相对方便的ajax来说明断点上传的思路。
- 首先是文件的识别,一个文件被分成了若干份之后如何告诉服务器你切了多少块,以及最终服务器应该如何把你上传上去的文件进行合并?
- 因此在文件开始上传之前,我们和服务器要有一个“握手”的过程,告诉服务器文件信息,然后和服务器约定切片的大小,当和服务器达成共识之后就可以开始后续的文件传输了。
- 前台要把每一块的文件传给后台,成功之后前端和后端都要标识一下,以便后续的断点。
- 当文件传输中断之后用户再次选择文件就可以通过标识来判断文件是否已经上传了一部分,如果是的话,那么我们可以接着上次的进度继续传文件,以达到续传的功能。
js如何处理防抖和节流?
- 在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。
- 此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。 函数防抖
- 函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
- 如下,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。
function debounce(fn, wait) {
var timeout = null;
return function() {
if(timeout !== null)
clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
// 处理函数 function handle() {
console.log(Math.random()); } // 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));
// 函数节流
- 函数节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。
- 节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。
- 如下,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。
var throttle = function(func, delay) {
var prev = Date.now();
return function() {
var context = this;
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
//总结
- 函数防抖:将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
- 函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。 区别:
- 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次
- Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
事件委托以及优缺点?
- 优点:
- 减少事件注册,节省内存。比如,
- 在table上代理所有td的click事件。
- 在ul上代理所有li的click事件。
- 简化了dom节点更新时,相应事件的更新。比如
- 不用在新添加的li上绑定click事件。
- 当删除某个li时,不用移解绑上面的click事件。 缺点:
- 事件委托基于冒泡,对于不冒泡的事件不支持。
- 层级过多,冒泡过程中,可能会被某层阻止掉。
- 理论上委托会导致浏览器频繁调用处理函数,虽然很可能不需要处理。所以建议就近委托,比如在table上代理td,而不是在document上代理td。
- 把所有事件都用代理就可能会出现事件误判。比如,在document中代理了所有button的click事件,另外的人在引用改js时,可能不知道,造成单击button触发了两个click事件。
介绍this各种情况?
- this的情况:
- 以函数形式调用时,this永远都是window
- 以方法的形式调用时,this是调用方法的对象
- 以构造函数的形式调用时,this是新创建的那个对象
- 使用call和apply调用时,this是指定的那个对象
- 箭头函数:箭头函数的this看外层是否有函数
- 如果有,外层函数的this就是内部箭头函数的this
- 如果没有,就是window
- 特殊情况:通常意义上this指针指向为最后调用它的对象。这里需要注意的一点就是如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例
== 和 ===的区别,什么情况下用相等==?
- ==
运算符称作相等,用来检测两个操作数是否相等,这里的相等定义的非常宽松,可以允许进行类型转换 - ===
用来检测两个操作数是否严格相等- 对于string,number等基础类型,==和===是有区别的 不同类型间比较,==之比较“转化成同一类型后的值”看“值”是否相等,===如果类型不同,其结果就是不等
- 同类型比较,直接进行“值”比较,两者结果一样
- 对于Array,Object等高级类型,==和===是没有区别的
- 基础类型与高级类型,==和===是有区别的 对于==,将高级转化为基础类型,进行“值”比较,因为类型不同,===结果为false
介绍下原型链(解决的是继承问题吗)
- JavaScript原型: 每个对象都会在其内部初始化一个属性,就是prototype(原型)。 原型链:
- 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
- 特点:
- JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
JS里垃圾回收机制是什么,常用的是哪种,怎么处理的?
JS的垃圾回收机制是为了以防内存泄漏,内存泄漏的含义就是当已经不需要某块内存时这块内存还存在着,垃圾回收机制就是间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的内存。
JS中最常见的垃圾回收方式是标记清除。
工作原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
-
工作流程
- 垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。
- 去掉环境中的变量以及被环境中的变量引用的变量的标记。
- 再被加上标记的会被视为准备删除的变量。
- 垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。
27.Promise和setTimeout的区别?
- 回顾JavaScript事件循环并发模型,我们了解了setTimeout和Promise调用的都是异步任务,这一点是它们共同之处,也即都是通过任务队列进行管理/调度。那么它们有什么区别吗?
- 任务队列
- 前文已经介绍了任务队列的基础内容和机制,可选择查看,本文对任务队列进行拓展介绍。JavaScript通过任务队列管理所有异步任务,而任务队列还可以细分为MacroTask
- Queue和MicoTask Queue两类。 MacroTask Queue MacroTask
- Queue(宏任务队列)主要包括setTimeout,setInterval, setImmediate,
- requestAnimationFrame, NodeJS中的`I/O等。 MicroTask Queue MicroTask
- Queue(微任务队列)主要包括两类: 独立回调microTask:如Promise,其成功/失败回调函数相互独立;
- 复合回调microTask:如 Object.observe, MutationObserver 和NodeJs中的
- process.nextTick ,不同状态回调在同一函数体; MacroTask和MicroTask
- JavaScript将异步任务分为MacroTask和MicroTask,那么它们区别何在呢? 依次执行同步代码直至执行完毕;
- 检查MacroTask 队列,若有触发的异步任务,则取第一个并调用其事件处理函数,然后跳至第三步,若没有需处理的异步任务,则直接跳至第三步;
- 检查MicroTask队列,然后执行所有已触发的异步任务,依次执行事件处理函数,直至执行完毕,然后跳至第二步,若没有需处理的异步任务中,则直接返回第二步,依次执行后续步骤;
- 最后返回第二步,继续检查MacroTask队列,依次执行后续步骤; 如此往复,若所有异步任务处理完成,则结束;
介绍下广度优先遍历(BFS)和深度优先遍历(DFS)?
广度优先遍历 英文缩写为BFS即Breadth FirstSearch。其过程检验来说是对每一层节点依次访问,访问完一层进入下一层,而且每个节点只能访问一次。对于上面的例子来说,广度优先遍历的
结果是:A,B,C,D,E,F,G,H,I(假设每层节点从左到右访问)。 先往队列中插入左节点,再插右节点,这样出队就是先左节点后右节点了。
广度优先遍历树,需要用到队列(Queue)来存储节点对象,队列的特点就是先进先出。例如,上面这颗树的访问如下:
首先将A节点插入队列中,队列中有元素(A);
将A节点弹出,同时将A节点的左、右节点依次插入队列,B在队首,C在队尾,(B,C),此时得到A节点;
继续弹出队首元素,即弹出B,并将B的左、右节点插入队列,C在队首,E在队尾(C,D,E),此时得到B节点;
继续弹出,即弹出C,并将C节点的左、中、右节点依次插入队列,(D,E,F,G,H),此时得到C节点;
将D弹出,此时D没有子节点,队列中元素为(E,F,G,H),得到D节点; 。。。以此类推。。
2.深度优先遍历 英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。对于上面的例子来说深度优先遍历的结果就是:A,B,D,E,I,C,F,G,H.(假设先走子节点的的左侧)。
深度优先遍历各个节点,需要使用到栈(Stack)这种数据结构。stack的特点是是先进后出。整个遍历过程如下:
先往栈中压入右节点,再压左节点,这样出栈就是先左节点后右节点了。 首先将A节点压入栈中,stack(A);
将A节点弹出,同时将A的子节点C,B压入栈中,此时B在栈的顶部,stack(B,C);
将B节点弹出,同时将B的子节点E,D压入栈中,此时D在栈的顶部,stack(D,E,C);
将D节点弹出,没有子节点压入,此时E在栈的顶部,stack(E,C); 将E节点弹出,同时将E的子节点I压入,stack(I,C);
…依次往下,最终遍历完成。 代码:也是以二叉树为例。
29.for in和for of的区别
- 推荐在循环对象属性的时候,使用for...in,在遍历数组的时候的时候使用for...of
- for...in循环出的是key,for...of循环出的是value
- 注意,for...of是ES6新引入的特性。修复了ES5引入的for...in的不足
- for...of不能循环普通的对象,需要通过和Object.keys()搭配使用
typeof和instanceof 区别?
- 在javascript中,判断一个变量的类型可以用typeof
- 数字类型、typeof返回的值是number。比如说:typeof(1),返回值是number
- 字符串类型,typeof返回的值是string。比如typeof(“123”返回值时string)
- 布尔类型,typeof返回的值是boolean。比如typeof(true)返回值时boolean
- 对象、数组、null返回的值是object。比如typeof(window),typeof(document),typeof(null)返回的值都是object
- 函数类型,返回的值是function。比如:typeof(eval),typeof(Date)返回的值都是function。
- 不存在的变量、函数或者undefined,将返回undefined。比如:typeof(abc)、typeof(undefined)都返回undefined
- 在javascript中,instanceof用于判断某个对象是否被另一个函数构造。
- 使用typeof运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回”object”。ECMAScript引入了另一个Java运算符instanceof来解决这个问题。Instanceof运算符与typeof运算符相似,用于识别正在处理的对象的类型。与typeof方法不同的是,instanceof方法要求开发者明确地确认对象为某特定类型
常见的继承有几种方法?
- 原型链继承
- 构造函数继承(经典继承)
- 组合方式继承(构造函数 + 原型链)
- es6方法继承
什么是事件冒泡,它是如何工作的?如何阻止事件冒泡?
在一个对象上触发某类事件(比如单击onclick事件),如果此对象定义了此事件的处理程序,那么此事件就会调用这个处理程序,如果没有定义此事件处理程序或者事件返回true,那么这个事件会向这个对象的父级对象传播,从里到外,直至它被处理(父级对象所有同类事件都将被激活),或者它到达了对象层次的最顶层,即document对象(有些浏览器是window)
//阻止事件冒泡的几种方法
//第一种
event.stopPropagation();
//第二种
return false;
//第三种
event.preventDefault();
什么是Promise对象,有哪些用法、通过Promise对象实现ajax
Promise是异步编程的一种解决方案,它是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。 Promise 提供统一的API,各种异步操作都可以用同样的方法进行处理。promise对象是一个构造函数,用来生成Promise实例;
-
Promise对象的特点
- 对象的状态不受外界影响,promise对象代表一个异步操作,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变就不会再变,任何时候都可以得到这个结果,promise对象的状态改变,只有两种可能:从pending变为fulfilled,从pending变为rejected。
-
Promise缺点。
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于pending状态时,无法得知目前进展到哪一个阶段
-
Promise对象的用法
-
是一个构造函数,这个构造函数里有两个参数,分别是:resolve(成功之后的回调函数)、reject(失败之后的回调函数)。
promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数
then方法可以接受连个回调函数作为参数,第一个回调函数是promise对象的状态变为resolved时调用,第二个回调函数是promise对象的状态变为rejected时调用,其中,第二个函数是可选的,不一定要提供,这两个函数都接受promise对象传出的值作为参数;then指定回调函数的时候,成功的回调函数必须传,失败的回调函数可以胜利。 如果前面的promise执行失败,不详影响后续操作终止,捕获异常的两种方式:
①可以为每个promise指定失败回调;
function(err){ console.log(……) })
②最后加catch(function(err){console.log(……) })//表示如前面有任意一个有报错,立即报错,并终止后面的;如果前面无报错,前面正常执行。
-
-
Promise对象实现ajax
什么是闭包,举个例子说明一下?
闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
创建闭包最常见方式,就是在一个函数内部创建另一个函数。下面例子中的 closure 就是一个闭包,
function func(){
va ra =1 ,b = 2;
funciton closure(){
return a+b;
}
return closure;
}
let、var、const区别
- var声明的变量会挂载在window上,而let和const声明的变量不会
- var声明变量存在变量提升,let和const不存在变量提升
- let和const声明形成块作用域,var变量提升不会形成作用域
- 同一作用域下let和const不能声明同名变量,而var可以
- var和let可以可以修改声明的变量,const不可以
- const定义的变量时必须初始化
- let、const 存在暂时性死区
apply、call、bind 什么区别?
这三者的作用就是改变函数运行时this的指向。
call和apply都是对函数的直接调用,而bind方法返回的仍然是一个函数,因此后面还需要()来进行调用才可以。
call后面的参数与say方法中是一一对应的,而apply的第二个参数是一个数组,数组中的元素是和方法中一一对应的。
-
bind传参
xw.say.bind(xh,"实验小学","六年级")(); xw.say.bind(xh)("实验小学","六年级");
-
call
语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
- 定义:调用一个对象的一个方法,以另一个对象替换当前对象。
- 说明:call方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
-
apply
语法:apply([thisObj[,argArray]])
- 定义:应用某一对象的一个方法,用另一个对象替换当前对象。
- 说明:如果argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。
-
bind
语法:bind(thisArg[, arg1[, arg2[, ...]]])
- 定义:将接受多个参数的函数变换成接受一个单一参数。
- 说明:bind()方法所返回的函数的length(形参数量)等于原函数的形参数量减去传入bind()方法中的实参数量(第一个参数以后的所有参数),因为传入bind中的实参都会绑定到原函数的形参。