也到了本身快找工做的时候了,因此最近在复习前端的知识,这里是本身对JavaScript的一些知识点的总结,之后会持续更新,分享本身的复习知识,有些是在不懂的时候参考大佬的讲解。有些理解不到位的地方还请指正。祝本身找工做顺利!!!javascript
这三个函数都会改变this的指向,call和apply更适用于在函数运行时改变this;而bind会返回一个新的函数,新函数的this由bind传入的参数决定,因此bind更适用于返回一个新函数,这个函数在未来才会执行,好比DOM添加事件。前端
// call
Function.prototype.myCall = function (ctx = window, ...arg) {
if (typeof this !== "function") return
ctx.fn = this
let res = ctx.fn(...arg)
delete ctx.fn
return res
}
// apply
Function.prototype.myApply = function (ctx = window, arg) {
if (typeof this !== "function") return
ctx.fn = this
if(!Array.isArray(arg)) {
throw new Error('须要数组')
}
let res = ctx.fn(...arg)
delete ctx.fn
return res
}
// bind
Function.prototype.newbBind = function(target){
target = target || window
var self = this;
// 这里的arguments是在调用时传入的参数
var args = [].slice.call(arguments, 1);
var temp = function () {}
function f(){
// 这里的arguments是bind返回的新函数传入的参
var _args = [].slice.call(arguments,0)//将一个类数组转化为数组
return self.apply(this instanceof temp? this : target, args.concat(_args))
}
temp.prototype = self.prototype
f.prototype = new temp()
return f
}
复制代码
在Lambda演算(一套数理逻辑的形式系统,具体我也没深刻研究过)中有个小技巧:假如一个函数只能收一个参数,那么这个函数怎么实现加法呢,由于高阶函数是能够当参数传递和返回值的,因此问题就简化为:写一个只有一个参数的函数,而这个函数返回一个带参数的函数,这样就实现了能写两个参数的函数了——这就是所谓的柯里化(Currying,以逻辑学家Hsakell Curry命名),也能够理解为一种在处理函数过程当中的逻辑思惟方式。java
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数且返回结果的新函数的技术。git
function curry(fn, args) {
var length = fn.length;
var args = args || [];
return function(){
newArgs = args.concat(Array.prototype.slice.call(arguments));
if (newArgs.length < length) {
return curry.call(this,fn,newArgs);
}else{
return fn.apply(this,newArgs);
}
}
}
function multiFn(a, b, c) {
return a * b * c;
}
var multi = curry(multiFn);
multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);
// 参考:https://juejin.im/post/5c9c3989e51d454e3a3902b6
复制代码
原型是function的一个属性,该属性本质上是一个对象,它定义了构造函数构造出来的共有祖先,构造函数产生的实例对象能够继承该属性的方法和属性,当实例访问某个属性找不到就会顺着原型链访问该属性。es6
有了原型,原型仍是一个对象,那么这个名为原型的对象天然还有本身的原型,这样的原型上还有原型的结构就构成了原型链。github
原型链是是描述实例对象与构造函数的原型之间的关系,若是实例对象找不到某个属性或者方法就会到构造函数的prototype上查找,若是仍是找不到就会访问构造函数prototype属性的__proto__
属性,直到null。面试
在JavaScript中没有类的概念,传统语言中类经过拷贝实现继承,JavaScript经过原型链、原型委托的方式实现继承。正则表达式
组合继承:编程
function Father() {
this.name = "father"
}
Father.prototype.say = function() {
console.log('say')
}
function Child() {
Father.call(this)
this.age = 12
}
Child.prototype = Object.create(Father.prototype)
Child.prototype.constructor = Child
复制代码
let inhert = (function() {
function F(){}
return function(father, child){
F.prototype = father.prototype
child.prototype = new F()
child.prototype.constructor = child
}
})
复制代码
参考:juejin.im/post/5c96d0…json
this是JavaScript中的一个关键字,被自动定义在全部函数的做用域中。**this是在运行的时候进行绑定,并非在编写的时候进行绑定,它的上下文取决于函数调用时的各类条件。**this的绑定和函数的声明位置无关,只取决于函数的调用方式。
当一个函数被调用的时候,会建立一个活动记录(也成为执行上下文)。这个记录会包含函数在哪里调用(调用栈)、函数调用的方法、传入的参数等信息。this就是记录中的一个属性,会在函数执行的过程当中用到。
调用位置指的是函数被调调用的位置而不是声明的位置。
默认绑定的时候this指向window,默认绑定是指函数不带任何修饰的函数引用进行调用。好比:
function foo() {
console.log(this)
}
foo() // window
复制代码
可是须要注意的是在严格模式下,默认绑定并不会指向window。
隐式绑定一般以对象做为执行上下文调用。可是咱们须要明白一个道理:无论是在对象中声明一个函数,仍是先定义再添加函数的引用,严格来叔这个函数都不属于该对象。
隐式绑定规则会把函数调用中的this绑定到这个上下文对象,由于调用foo的时候this被绑定到该对象,所以this.a等同于obj.a。
对象属性引用链中只有最后一层会影响调用的位置。
let obj2 = {
a:2,
foo1:foo1
}
let obj1 = {
a:1,
obj2:obj2
}
function foo1() {
console.log(this.a)
}
obj1.obj2.foo1() // 2
复制代码
var a = 'window'
let obj = {
a: 'obj',
foo() {
console.log(this.a)
}
}
let bar = obj.foo
bar()
复制代码
由于bar是obj.foo的一个引用,可是实际上引用的是foo函数的自己,所以bar()是一个不带任何修饰符的调用因此是默认绑定,this指向window。
var a = 'window'
let obj = {
a: 'obj',
foo() {
console.log(this.a)
}
}
function doFoo(fn) {
fn()
}
doFoo(obj.foo)
复制代码
这里调用doFoo的时候参入了obj.foo做为实参,并将obj.foo赋值给fn,因此fn是foo函数的引用,在调用fn的时候也是不带任何修饰的调用,因此是默认调用this指向window。
如下这种状况this也是指向window。缘由和上面同样。
var a = 'window'
let obj = {
a: 'obj',
foo() {
console.log(this.a)
}
}
setTimeout(obj.foo, 1000)
复制代码
因此上面咱们能够看出回调函数丢失this是很是常见的。
若是把null或者undefined做为this绑定的对象传入其中,这些值会被忽略,其实是默认绑定。
new > call、apply、bind > 隐式绑定 > 默认绑定
节流(throttle)是防止用户频繁操做,形成浏览器性能消耗过大
防抖(debounce),就是指触发事件后在 n 秒内函数只能执行一次,若是在 n 秒内又触发了事件,则会从新计算函数执行时间。
// 节流函数(throttle)
function throttle (fn, wait=500) {
let pre_time = 0
return function(...arg) {
let curr_time = Date.now()
if(curr_time - pre_time > wait) {
fn.apply(this, arg)
pre_time = curr_time
}
}
}
// 防抖函数(debounce)
function debounce(fn, wait = 500, immediately = true) {
let timer
return function(...arg) {
if(immediately) {
fn.apply(this, arg)
immediately = false
}
clearTimout(timer)
timer = setTimout(()=> {
fn.apply(this, arg)
}, wait)
}
}
复制代码
一、了解 Promise 吗?
二、Promise 解决的痛点是什么?
三、Promise 解决的痛点还有其余方法能够解决吗?若是有,请列举。
四、Promise 如何使用?
五、Promise 经常使用的方法有哪些?它们的做用是什么?如何使用?
六、Promise 在事件循环中的执行过程是怎样的?
七、Promise 的业界实现都有哪些?
八、能不能手写一个 Promise ?
function myPromise(constructor){
let self=this;
self.status="pending" //定义状态改变前的初始状态
self.value=undefined;//定义状态为resolved的时候的状态
self.reason=undefined;//定义状态为rejected的时候的状态
function resolve(value){
//两个==="pending",保证了状态的改变是不可逆的
if(self.status==="pending"){
self.value=value;
self.status="resolved";
}
}
function reject(reason){
//两个==="pending",保证了状态的改变是不可逆的
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
}
}
//捕获构造异常
try{
constructor(resolve,reject);
}catch(e){
reject(e);
}
}
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
// 来源:https://github.com/forthealllight/blog/issues/4
复制代码
promise是异步编程的一种解决方案,解决了回调地狱的问题。Promise是一个容器保存着某个将来才会结束的事件的结果,也能够说是一个对象从它能够获取异步操做的消息。
特色:
pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)三种状态。pending
(进行中)到fulfilled
(已成功)或pending
(进行中)到reject
(以失败)。const promise = new Promise(function(resolve, reject) {
if(/*success*/) {
resolve(val)
} else {
reject(val)
}
})
复制代码
Promise接受一个函数做为参数,该函数接受两个参数,它们是两个函数,由 JavaScript 引擎提供,不用本身部署。
resolve的做用是在异步操做成功的时候调用,并将异步操做的结果做为参数传递出去;reject是在异步操做失败的时候调用。
Promise实例生成以后能够用then方法分别指定成功和失败的回调函数。
promise.then(function() {
/*success*/
}, function() {
/*failure*/
})
复制代码
第一个参数是成功时调用,第二个是失败时调用,这两个函数都接受Promise对象传出的值做为参数,第一个成功时的回调函数时必须的失败时的回调函数不是必须的。
resolve
函数的参数除了正常的值之外,还多是另外一个 Promise 实例,好比像下面这样。
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
// 这里p2的状态决定p1的状态,p2后的then都是针对p1的
复制代码
这里p2的状态决定p1的状态,p2后的then都是针对p1的
Promise的具体例子:
function timeout(ms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms, 'done');
})
}
let p = timeout(100).then((val) => {
console.log(val)
})
复制代码
Promise建立以后会当即执行
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// promise
// Hi!
// resolved.
复制代码
实现Ajax
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
复制代码
then方法是定义在原型对象Promise.prototype
上的,它的做用是为 Promise 实例添加状态改变时的回调函数。前面说过,then
方法的第一个参数是resolved
状态的回调函数,第二个参数(可选)是rejected
状态的回调函数。
then方法也能够返回一个新的Promise实例,所以能够采用链式调用:
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function funcA(comments) {
console.log("resolved: ", comments);
}, function funcB(err){
console.log("rejected: ", err);
});
复制代码
用于错误的捕获
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
复制代码
上面代码中,第二种写法要好于第一种写法,理由是**第二种(catch)写法能够捕获前面then
方法执行中的错误,**也更接近同步的写法(try/catch
)。所以,建议老是使用catch
方法,而不使用then
方法的第二个参数。
finally方法用于执行无论最后状态如何,都会执行的操做。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
复制代码
该方法用于将多个Promise实例包装成一个新的Promise实例。
const p = Promise.all([p1, p2, p3])
复制代码
Promise.all()接受一个数组,数组的值都是Promise对象,若是不是则会调用Promise.resolve()方法。(Promise.all
方法的参数能够不是数组,但必须具备 Iterator 接口,且返回的每一个成员都是 Promise 实例。)
p
的状态由p1
、p2
、p3
决定,分红两种状况。
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
注意,若是做为参数的 Promise 实例,本身定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法。
Promise.race
方法一样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3])
复制代码
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。和Promise同样,数组的值必须是promise对象,若是不是则会调用Promise.resolve()方法。
Promise.resolve()方法能够将现有的对象转换为Promise对象。
Promise.resolve('foo')
// 等同于
new Promise(function(resolve) {
resolve('foo')
})
复制代码
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
。
let p = Promise.reject('foo')
// 等同于
new Promise((resolve, reject) => reject('foo'))
复制代码
由于数组和对象都是引用值,因此当咱们直接使用=赋值,会是两个对象的指针指向同一个空间,当咱们改变其中一个值的时候,另外一个对象也会受到影响。当咱们使用深拷贝从新开辟了一个内存空间,将该对象的指针指向新开辟的空间。
针对数组咱们可使用[...arr]
针对对象咱们可使用Object.assign({}, obj)、{...obj}
以上两种都是浅拷贝
也可使用JSON.parse(JOSN.stringify(obj))
function deepClone(obj) {
let res
if(typeof obj === "object") {
res = obj.constructor = Array?[]:{}
for(let i in obj) {
res[i] = typeof obj[i] === "object"?deepClone(obj[i]):obj[i]
}
} else {
res = obj
}
return obj
}
复制代码
JavaScript将任务分为同步任务和异步任务,在第一次执行的时候会将整个script代码看做宏任务,同步任务进入主线程,异步任务进入Event Table注册,当知足条件异步任务的回调函数加入到Event Queue队列中,当主线程空闲的时候,会从Event Queue取出对应的函数。宏任务(script、setTimeout)和微任务(Promise、process.nextTick)分别进入不一样的Event Table,它们的执行顺序不同,当主线程空闲的时候首先会清空微任务队列,而后再拿出一个宏任务队列的函数,而后再检查微任务队列,如此循环。
做用域是在运行时代码中的特定变量的有效范围。做用域决定了代码区块中变量和其余资源的可见性。做用域内层能够看见做用域外层,做用域外层不能看见做用域外层,因此做用域在不一样做用域中声明的变量不会形成污染和命名冲突。
定义在最外层的函数和变量,未经声明就赋值的变量,window的属性。这里须要注意的是var声明的全局变量以及未经声明就赋值的变量会挂载到window属性上,可是var声明的变量不能删除,未经声明的变量能够删除。
当函数执行的时候就会在内部建立一个函数做用域,当函数执行完成就会销毁该做用域。
在ES6以前是没有块级做用域的,ES6引入了let、const关键字就能够建立块级做用域。
当在一个函数内部搜索一个变量的时候,若是该函数没有声明该变量,那么就会顺着代码执行环境建立的做用域逐层向外搜索,一直搜索到全局做用域。
解释阶段:
执行阶段
JavaScript在解释阶段便会肯定做用域规则,可是执行上下文是在函数执行的前一刻。
执行上下文最明显的就是this指向是在执行的时候肯定的。
区别:执行上下文在运行时肯定,随时能够改变;做用域在定义时就肯定,而且不会改变。同一做用域下,不一样的调用会产生不一样的执行上下文,从而产生不一样的结果。
当函数能够记住并访问所在的词法做用域时,就产生了闭包,即便函数是在当前词法做用域外执行。简单讲,闭包就是指有权访问另外一个函数做用域中的变量的函数。
let a = 1
function foo() {
console.log(a)
}
function bar() {
let a = 2
foo()
}
bar() // 1
复制代码
let a = 'window'
function foo() {
let a = 'foo'
return function() {
console.log(a)
}
}
let bar = foo()
bar() // foo
复制代码
dom.onclick = function(){}
dom.addEventListenner('click',function(){})
dom.addEventListenner('keyup',fuction(){})
,增长了事件类型dom.addEventListenner('keyup',fuction(){}, false|true)
,第三个参数为false表示在冒泡阶段触发,第三个参数为true表示在捕获阶段触发。先捕获后冒泡。
dom.addEventListenner('keyup',fuction(){}, true)
dom.addEventListenner('keyup',fuction(){}, false)
事件委托就是基于事件冒泡的,当子元素触发点击事件会冒泡到父元素,而后经过e.target来判断子元素。
经过冒泡或者捕获怎么到达目标对象的阶段?
事件首先经过捕获到达目标元素,再经过目标元素冒泡到window对象,即先捕获后冒泡。
参考:www.jianshu.com/p/71bb3cf19…
// 1.第一种
// 定义
let eve = new Event('coustome')
// 绑定
dom.addEventListenner('coustome', function(){})
// 触发
dom.dispatch(eve)
// 2.第二种,能够添加数据
let eve1 = new CustomoeEvent('coustome', {data})
复制代码
__proto__
属性指向函数的prototype
属性function New(fn, ...arg) {
let res = {}
if(fn.prototype !== null) {
res = Object.create(fn.prototype)
}
let ret = fn.apply(res, arg)
if(ret === "object" || ret === "function" && ret !== null) {
return ret
}
retrun res
}
复制代码
语法:let str = new String('hello world')
当咱们声明一个字符串变量的时候let str1 = 'hello'
,这是字面量的形式,而且是一个不可变的值。咱们访问str1.length
属性、或其余属性的时候,就会把该变量转换成为一个String对象(这里一般叫作包装类),由于声明的字符串没有该属性,只有转换为包装类才有。在JavaScript中会把字符串字面量转化成String对象。
null在数值转换时被转换为0,undefined会被转换为NaN
undefined只有一个值,即undefined。如下状况会出现undefined:
null也只有一个值,可是当咱们执行typeof null
的时候,会返回object。咱们能够理解为null是一个空指针对象,尚未保存对象。如下几种状况会使用出现null:
不能区别null、对象、数组、正则表达式等
是基于原型链操做的:A instanceof B,判断A的原型链上有没有B的原型
比较好的方法,可是IE6/7/8中 Object.prototype.toString.apply(null)返回“[object Object]”。
对象声明可使用字面量形式和构造函数形式
let obj = {}
let obj1 = new Object()
复制代码
这两种方法生成的对象是同样的,区别在于字面量形式能够添加多个键值对、构造函数形式只能逐个添加。
JavaScript还有一些对象子类型,一般被称为内置对象。
对象中的值一般不会存储在对象内部,一般状况下,存储在对象容器内部的是这些属性的名称,它们就像指针同样,指向这些值的真正存储位置。
let obj = {
a:1
}
obj.a
obj['a']
复制代码
obj.a,的语法被称为属性访问,obj['a']的方法被称为键访问,它们在大都数状况下是能够互换的,区别在于.a要符合命名的规范性,['a']能够接受任意的UTF-8/Unicode字符做为属性名。好比"super-Fun!",这时候就不可使用属性访问了。
注意:在对象中属性名永远都是字符串,若是不是者会被转换为字符串。
let a = "foo"
let obj = {
[a + '1']: 'hello',
[a + '2']: 'hello2'
}
复制代码
一、查看属性描述符:Object.getOwnPropertyDescriptor(obj, props)
语法:
let myObj = {
a: 1
}
console.log(Object.getOwnPropertyDescriptor(myObj, 'a'))
复制代码
二、设置属性描述符:Object.defineProperty(obj, props)
语法:
Object.defineProperty(myObj, 'b', {
value:2,
writable: false,
configurable: true,
enumerable: true
})
复制代码
因此咱们经过设置writable,configurable为false来设置一个对象常量。
一、经过设置writable,configurable为false来设置一个对象常量。
二、禁止拓展:Object.preventzectensions(obj)
语法:
let myObj1 = {
a: 1
}
Object.preventExtensions(myObj1)
myObj1.b = 2
myObj1.b // undefined
复制代码
三、密封:Object.seal(obj)
实际上这个方法会调用Object.preventzectensions(obj)方法,并将现有属性的configurable设为false,因此密封以后既不能添加新的属性,也不能删除和配置现有属性。
四、冻结:Object.freeze(obj)
这个方法会调用Object.seal()方法,并将现有属性的writable设为false,故既不能添加新的属性,也不能删除、配置、修改现有属性。
get、set会劫持你对对象数据的操做。
let data = {}
Object.defineProperty(data, 'key', {
// value: 1,
enumerable: true,
configurable: false, // 不能再定义
get: function () {
// Dep.target && dep.addDep(Dep.target)
return this.value
},
set: function (newVal) {
if (newVal === this.value) {
return
}
console.log(`发生了变化${this.value}=>${newVal}`)
this.value = newVal
// dep.notify() // 通知全部订阅者
}
})
复制代码
一、in:检查对象及原型链
二、hasOwnProperty()
具备length属性,能够经过数字下标访问元素,如arguments、获取的DOM节点。Array.from(arguments)能够将一个类数组转化为数组
// es6最简单的方式
[...new Set(arr)]
function unique(arr) {
let list = [...arr]
let res = []
list.forEach(item => {
if(!res.include(item)) {
res.push(item)
}
})
return res
}
复制代码