JavaScript基础
执行上下文/作用域链/闭包
执行上下文
概念:执行上下文是评估和执行JavaScript代码的环境的抽象概念,每当JavaScript代码运行时都是在执行上下文中运行;
类型有3中:
1.全局执行上下文
2.函数执行上下文
3.eval函数执行上下文
作用域链
作用域链的作用是保证执行环境里有权访问的变量和函数是有限的,作用域链的变量只能向上访问,直到window对象为止,作用域链向下访问是不被允许的,作用域的作用是控制变量和函数的可见性和生命周期;
闭包
概念:闭包是指有权访问另一个函数作用域中变量的函数;
创建闭包的常见方式是在一个函数中建另一个函数,里面函数引用外层函数的变量;
闭包形成的原因:
函数在创建时,都会形成一个执行环境和作用域链,函数的作用域链包含指向外部函数的执行环境的指针,外部函数的作用域指向外部的外部,一直到全局指向函数;
闭包的优点:可以避免全局变量的污染,建立私有化的变量和方法;
闭包的缺点:(1)匿名函数的this指向全局window,解决方法:var that = this; (2)如果闭包的作用域链中包含HTML元素,导致无法减少元素引用的次数,垃圾回收机制不能回收(使用不当容易造成内存泄漏);
this/call/apply/bind
介绍JavaScript中的this
全局中this指向window;
非严格模式下函数中的this指向window;
严格模式下函数中的this指向undefined;
方法中this指向所有者对象;
事件中this指向接收事件的元素;
箭头函数没有自己的this,箭头函数的this指向定义时的this而不是运行时的this;
如何改变this指向
obj1.test.call(obj2,arg1,arg2)
obj1.test.apply(obj2,[arg1,arg2])
call和apply的区别
传递参数的不同:call接收一个一个参数,apply接收数组
如何实现call如何实现apply
实现call:
Function.prototype.myCall = function(context){
context.fn = this; //(this表示function test(){})
var args = […arguments].slice(1)
return context.fn(…args)
}
obj1.test.myCall(obj2,arg1,arg2)
实现apply
Function.prototype.myApply = function(context){
context.fn = this
var result = context.fn(…arguments[1])
delete context.fn
return result
}
如何实现bind
//手动实现bind:
Function.prototype.myBind = function(context){
if(typeof this !== ‘function’){
throw new TypeError(‘Error’)
}
//context相当于obj2(上下文)
var _this = this//function(age,age2){}
var args = […arguments].slice(1)//[123,456]
return function F(){
if(this instanceof F){//因为在外面调用函数F(),此处的this相当于window
return new _this(…args,…arguments)
}
return _this.apply(context,args.concat(…arguments))
}
}
原型/继承
JavaScript中的原型
__proto__和constructor是对象独有的;
(contructor表示指向该对象的构造函数)
prototype属性是函数所独有的,但由于JS中函数也是对象的一种,所以函数也有__proto_和constructor属性;
__proto__和prototype关系举例:
eg.
function Foo(){};
var f = new Foo();
f.proto == Foo.prototype;//true
f.constructor == Foo;//true
原型链是什么
每个对象在其内部都会初始化一个__proto__属性,当访问对象的某个属性时,如果对象上找不到这个属性,就会去对象的__proto__上去找这个属性,如果对象的__proto__也找不到这个属性,就去对象的__proto__的__proto__,这样一层一层向上找,直到Object,在Object没有找到返回null,形成一条__proto__连起来的链条。
如何利用原型实现继承
实现原型继承的方法:让原型对象等于另一个对象的实例
promise
promise是什么
promise是异步编程的一种解决方案。
有三种状态fulfilled,rejected,pending,状态的改变有两种:1.pending–>fulfilled
2.pending–>rejected
状态一旦改变便不再变,promise构造函数接收两个参(resolve,reject)
new Promise()–>执行异步操作?成功了-》执行resolve()->Promise对象(resolved状态)->回调onResolved()(then里面的)–>
失败了-》执行reject() --》Promise对象Rejected状态------>回调onRejected()—>返回promise
为什么要用Promise?(promise指定回调函数的方式更加灵活,具体如下)
旧的:因为纯回调函数执行异步操作之前就要指定回调函数,顺序为先指定回调函数,再启动异步任务(如下图);
优点一:Promise:启动异步任务–》返回Promise对象–》给Promise绑定回调函数(一旦得到promise说明启动了异步操作)
优点二:解决回调地狱问题(回到地狱是指回调函数嵌套使用,外部回调函数的异步执行结果 作为 内部回调函数的条件,不便于阅读,不便于异常的处理(每一层都需要调用异常处理的回调))
解决方案:promise的链式调用
终极解决:async/await
如何实现promise
//异步链式调用的Promise
function MyPromise(executor){
var self = this
self.onResolvedCallback = []
function resolve(value){
setTimeout(()=>{
self.data = value
self.onResolvedCallback.forEach(callback => callback(value))
})
}
executor(resolve.bind(self))
}
MyPromise.prototype.then = function(onResolved){
var self = this
return new MyPromise(resolve => {
self.onResolvedCallback.push(function(){
var result = onResolved(self.data)
if(result instanceof MyPromise){
result.then(resolve)
}else{
resolve(result)
}
})
})
}
//上述代码链接:https://juejin.im/post/5e6f4579f265da576429a907#comment
//例子测试:
new MyPromise((resolve)=>{
setTimeout(()=>{
console.log('立即执行器1')
resolve(2)
},1000)
}).then(res=>{
console.log(`resolve传递过来的值:${res}`)
return new MyPromise((resolve)=>{
setTimeout(()=>{
resolve(3)
},1000)
})
}).then(res2=>{
console.log(res2)
})
async/await
1.概念:async是什么?
async就是Generator函数的语法糖。
1.1 async对于Generator的改进在于:
(1)内置执行器(async带有内置执行器,而generator函数必须依靠执行器-Co模块)
(2)更好的语义
(3)更广泛的适用性(Co模块规定yield后面必须是Thunk函数或者Promise对象,而await后面可以是Promise,也可以是原始类型的值,但这时等于同步操作)
(4)返回值是Promise,可以用then方法指定下一步操作(generator返回值是Iterator对象),进一步说,async可以看做是多个异步操作包装成的Promise对象,await命令就是内部then命令的语法糖。
2.使用时需要注意:
await后面的异步操作出错等同于async返回的promise被reject,防止出错的方法是将await放到try catch中。
3.实现原理:async的实现原理就是将Generator函数和自动执行器包装在一个函数里。
深浅拷贝
介绍深拷贝和浅拷贝
1.浅拷贝概念(只进行一层级的拷贝):创建一个新对象,这个对象有着原始对象属性的精准拷贝。如果属性是基本类型,拷贝的是基本类型的值,如果属性是引用类型,拷贝的是引用类型的内存地址,如果其中某一个对象改变了这个值,就会影响到另一个对象。
2.深拷贝概念(无限层级的拷贝):将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响到原来对象。
如何实现浅拷贝
浅拷贝的实现:
1.首先是JavaScript中自带的:数组的slice方法,数组的concat方法。
举例子:
2.for循环的实现:
//1.浅复制
var obj1 = {
name: 'Hu',
age: 26,
friends: ['ss',['aa','ff'],'xx']
}
function shallowCopy(src){
var dst = {};
for(var prop in src){
if(src.hasOwnProperty(prop)){
dst[prop] = src[prop];
}
}
return dst;
}
var obj3 = shallowCopy(obj1);
obj3.name = 'yy';
console.log(obj3);//{name: "yy", age: 26, friends: Array(3)}
console.log(obj1);//{name: "Hu", age: 26, friends: Array(3)}
//2.注意 直接赋值操作与浅复制不同:
var obj2 = obj1;
obj2.name = 'yy1';
console.log(obj1);//{name: "yy1", age: 26, friends: Array(3)}
console.log(obj2);//{name: "yy1", age: 26, friends: Array(3)}
//直接赋值会同时改变原始对象,因为二者引用指向的是同一个对象;
如何实现深拷贝
1.JavaScript中自带的:JSON序列化:
JSON.parse(JSON.stringfy(obj1));
JSON序列化实现深拷贝的问题:在遇到undefined 、Symbol、该对象不能正常的序列化。
2.
//深拷贝
function deepCopy(m){
var target = {}
for(var i in m){
if(m.hasOwnProperty(i)){
if(typeof m[i] == 'object'){
target[i] = deepCopy(m[i])
}else{
target[i] = m[i]
}
}
}
return target
}
var a2 = deepCopy(a11)
console.log(a2.b.c === a11.b.c)
实现深拷贝需要注意
Object.assign(target,source)只深拷贝了原始对象的第一层级,后面的层级都是浅拷贝;
eg:
var obj11 = {};
Object.assign(obj11,obj1);
obj11.name = 'hust';
obj11.age = 25;
obj11.friends[2] = [1,2];
console.log(obj11);//{name: "hust", age: 25, friends: Array(3)}
//对于对象中的引用类型Object.assign实现的是浅拷贝,原始对象中的数组也随之改变了;
//0: "ss"
//1: (2) ["aa", "ff"]
//2: (2) [1, 2]
console.log(obj1);//{name: "Hu", age: 26, friends: Array(3)}
//0: "ss"
//1: (2) ["aa", "ff"]
//2: (2) [1, 2]
解决循环引用的问题
事件机制Event Loop
参考掘金: https://juejin.im/post/5c3d8956e51d4511dc72c200
Event Loop事件循环,指浏览器或者Node一种解决单线程运行不会阻塞的机制,也就是我们经常使用的异步原理。
在JavaScript中任务被分为两种:宏任务和微任务。
如何实现一个事件的发布订阅
事件循环
事件轮询:监听调用栈并确定调用栈是否为为空,如果为空就检查消息队列是否有挂起的回调函数等待执行。
宏任务和微任务的区别
宏任务:每次执行栈中执行的代码可以理解为一个宏任务。
宏任务有哪些:script整体代码,setTimeout,setInterval, setImmediate(浏览器暂不支持,只有IE10支持),I/O,UIRendering
点击事件属于宏任务:点击事件属于事件触发线程,用来控制事件循环(如点击事件,ajax)(所以点击事件是宏任务);
微任务:当前宏任务执行完之后立即执行的任务。
微任务有哪些:process.nextTick() promise,MutationObserver(用来监视DOM变动,不会马上出发等到所有的DOM操作结束后才触发),ObjectObserver(废弃)
nextTick()执行顺序:优先于其他微任务先先执行,process.nextTick() 将callback添加到回调队列,如果存在nextTick队列就会清空队列中所有的回调函数,并优先于其他的micro先执行;
函数式编程
Web Worker
二级目录
二级目录
常用方法
ES6
--未更新完