如图所示,JS的三座大山:

  • 同步、异步
  • 作用域、闭包
  • 原型、原型链

1. 同步、异步
JavaScript执行机制,重点有两点

  • JavaScript是一门单线程语言
  • Event Loop(事件循环)是JavaScript的执行机制

JS为什么是单线程

最初设计JS是用来在浏览器验证表单操控DOM元素的是一门脚本语言,如果js是多线程的,那么两个线程同时对一个DOM元素进行了相互冲突的操作,那么浏览器的解析器是无法执行的。

js为什么需要异步

如果js中不存在异步,只能自上而下执行,如果上一行解析时间很长,那么下面的代码就会被阻塞。
对于用户而言,阻塞就以为着“卡死”,这样就导致了很差的用户体验。比如在进行ajax请求的时候如果没有返回数据后面的代码就没办法执行

JS的事件循环(eventloop)是怎么运作的

  • 1.首先判断JS是同步还是异步,同步就进入主线程运行,异步就进入event table.

  • 2.异步任务在event table中注册事件,当满足触发条件后,(触发条件可能是延时也可能是ajax回调),被推入event queue

  • 3.同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主线程中。
    如图所示:

那怎么知道主线程执行栈为空呢?js引擎存在monitoring process进程,会持续不断的检查 主线程执行栈是否为空,一旦为空,就会去event queue那里检查是否有等待被调用的函数

  • 宏任务 包含整个script代码块,setTimeout, setIntval
  • 微任务 Promise , process.nextTick

在划分宏任务、微任务的时候并没有提到async/ await的本质就是Promise

setTimeout(function() {console.log('4')
})new Promise(function(resolve) {console.log('1') // 同步任务resolve()
}).then(function() {console.log('3')
})
console.log('2')
执行结果: 1-2-3-4
1. 这段代码作为宏任务,进入主线程。
2. 先遇到setTimeout,那么将其回调函数注册后分发到宏任务event queue.
3. 接下来遇到Promise, new Promise立即执行,then函数分发到微任务event queue
4. 遇到console.log(), 立即执行
5. 整体代码script作为第一个宏任务执行结束, 查看当前有没有可执行的微任务,执行then的回调。(第一轮事件循环结束了,我们开始第二轮循环)
6. 从宏任务的event queue开始,我们发现了宏任务event queue中setTimeout对应的回调函数,立即执行。
console.log('1')
setTimeout(function() {console.log('2')process.nextTick(function() {console.log('3')})new Promise(function(resolve) {console.log('4')resolve()}).then(function() {console.log('5')})
})process.nextTick(function() {console.log('6')
})new Promise(function(resolve) {console.log('7')resolve()
}).then(function() {console.log('8')
})setTimeout(function() {console.log('9')process.nextTick(function() {console.log('10')})new Promise(function(resolve) {console.log('11')resolve()}).then(function() {console.log('12')})
})
1.整体script作为第一个宏任务进入主线程,遇到console.log(1)输出1遇到setTimeout, 其回调函数被分发到宏任务event queue中。我们暂且记为setTimeout1
3.遇到process.nextTick(),其回调函数被分发到微任务event queue中,我们记为process1
4.遇到Promise, new Promise直接执行,输出7.then被分发到微任务event queue中,我们记为then1
又遇到setTimeout,其回调函数被分发到宏任务event queue中,我们记为setTimeout2.
现在开始执行微任务, 我们发现了process1和then1两个微任务,执行process1,输出6,执行then1,输出8, 第一轮事件循环正式结束, 这一轮的结果输出1,7,6,8.那么第二轮事件循环从setTimeout1宏任务开始
5. 首先输出2, 接下来遇到了process.nextTick(),统一被分发到微任务event queue,记为process2
8new Promise立即执行,输出4,then也被分发到微任务event queue中,记为then2
6. 现在开始执行微任务,我们发现有process2和then2两个微任务可以执行输出3,5. 第二轮事件循环结束,第二轮输出2,4,3,5. 第三轮事件循环从setTimeout2哄任务开始
10。 直接输出9,跟第二轮事件循环类似,输出9,11,10,12
7. 完整输出是1,7,6,8,2,4,3,5,9,11,10,12(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)

async/await用来干什么

用来优化promise的回调问题,被称为是异步的终极解决方案

async/await内部做了什么

async函数会返回一个Promise对象,如果在函数中return一个直接量(普通变量),async会把这个直接量通过Promise.resolve()封装成Promise对象。如果你返回了promise那就以你返回的promise为准。await是在等待,等待运行的结果也就是返回值。await后面通常是一个异步操作(promise),但是这不代表await后面只能跟异步才做,await后面实际是可以接普通函数调用或者直接量。
async相当于 new Promise,await相当于then

await的等待机制

如果await后面跟的不是一个promise,那await后面表达式的运算结果就是它等到的东西,如果await后面跟的是一个promise对象,await它会’阻塞’后面的diamante,等着promise对象resolve,
然后得到resolve的值作为await表达式的运算结果。但是此"阻塞"非彼“阻塞”,这就是await必须用在async函数中的原因。
async函数调用不会造成"阻塞",它内部所有的“阻塞”都被封装在一个promise对象中异步执行(这里的阻塞理解成异步等待更合理)

async/await在使用过程中有什么规定

每个async方法都返回一个promise, await只能出现在async函数中

async/await在什么场景使用

单一的promise链并不能发现async/await的有事,但是如果需要处理由多个promise组成的then链的时候,优势就能体现出来了(Promise通过then链来解决多层回调的问题,现在又用async/awai来进一步优化它)

2. 作用域、闭包
闭包

  • 闭包是指有权访问另外一个函数作用域中的变量的函数(红宝书)
  • 闭包是指那些能够访问自由变量的函数。(MDN)其中自由变量, 指在函数中使用的,但既不是函数参数arguments也不是函数的局部变量的变量,其实就是另外一个函数作用域中的变量。)
    作用域
  • 说起闭包,就必须要说说作用域,ES5种只存在两种作用域:
    1.函数作用域。
    2.全局作用域 当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到, 就去父作用域找, 直到找到该变量的标示符或者不在父作用域中,
    3.这就是作用域链,每一个子函数都会拷贝上级的作用域, 形成一个作用域的链条。
let a = 1;function f1() {var a = 2function f2() {var a = 3;console.log(a); //3}
}

在这段代码中,

  • f1的作用域指向有全局作用域(window) 和它本身,
  • 而f2的作用域指向全局作用域(window)、 f1和它本身。
  • 而且作用域是从最底层向上找, 直到找到全局作用域window为止,
  • 如果全局还没有的话就会报错。闭包产生的本质就是,
  • 当前环境中存在指向父级作用域的引用。
function f2() {var a = 2function f3() {console.log(a); //2}return f3;
}
var x = f2();
x();

这里x会拿到父级作用域中的变量, 输出2。

因为在当前环境中,含有对f3的引用, f3恰恰引用了window、 f3和f3的作用域。
因此f3可以访问到f2的作用域的变量。那是不是只有返回函数才算是产生了闭包呢?回到闭包的本质,只需要让父级作用域的引用存在即可。

var f4;function f5() {var a = 2f4 = function () {console.log(a);}
}
f5();
f4();

让f5执行,给f4赋值后,等于说现在f4拥有了window、f5和f4本身这几个作用域的访问权,还是自底向上查找,最近是在f5中找到了a,因此输出2。在这里是外面的变量f4存在着父级作用域的引用,
因此产生了闭包,形式变了,本质没有改变。

场景

  • 返回一个函数。
  • 作为函数参数传递。
  • 在定时器、 事件监听、 Ajax请求、 跨窗口通信、 Web Workers或者任何异步中,只要使用了回调函数, 实际上就是在使用闭包。IIFE(立即执行函数表达式) 创建闭包, 保存了全局作用域window和当前函数的作用域。
 var b = 1;function foo() {var b = 2;function baz() {console.log(b);}bar(baz);}function bar(fn) {// 这就是闭包fn();}// 输出2,而不是1foo();// 以下的闭包保存的仅仅是window和当前作用域。// 定时器setTimeout(function timeHandler() {console.log('111');}, 100)// 事件监听// document.body.click(function () {//     console.log('DOM Listener');// })// 立即执行函数var c = 2;(function IIFE() {// 输出2console.log(c);})();

经典的一道题

for (var i = 1; i <= 5; i++) {setTimeout(function timer() {console.log(i)}, 0)
}  // 6 6 6 6 6 6
// 为什么会全部输出6? 如何改进, 让它输出1, 2, 3, 4, 5?

解析:

  • 因为setTimeout为宏任务, 由于JS中单线程eventLoop机制, 在主线程同步任务执行完后才去执行宏任务。
  • 因此循环结束后setTimeout中的回调才依次执行, 但输出i的时候当前作用域没有。
    往上一级再找,发现了i,此时循环已经结束,i变成了6,因此会全部输出6。
    利用IIFE(立即执行函数表达式)当每次for循环时,把此时的i变量传递到定时器中
 for (var i = 0; i < 5; i++) {(function (j) {setTimeout(() => {console.log(j)}, 1000);})(i)}

给定时器传入第三个参数, 作为timer函数的第一个函数参数

for (var i = 0; i < 5; i++) {setTimeout(function (j) {console.log(j)}, 1000, i);}

使用ES6中的let

let使JS发生革命性的变化, 让JS有函数作用域变为了块级作用域,
用let后作用域链不复存在。 代码的作用域以块级为单位,

for (let i = 1; i <= 5; i++) {setTimeout(function timer() {console.log(i)}, 2000)
}

3.原型、原型链
原型(prototype)

JS中所有函数都会有prototype属性,只有函数才有
其所有的属性和方法都能被构造函数的实例对象共享访问

代码如下:

function Person(name){this.name = name}Person.prototype.sayHello(){console.log('sayHello')}let p1 = new Person();let p2 = new Person();console.log(p1.sayHello) //sayHelloconsole.log(p2.sayHello) //sayHello

构造函数(constructor)

JS中constructor存在每个函数的prototype属性中,其保存了指向该函数的引用

Person.prototype.constructor ==Person   //true

原型链(_ _ proto _ _)

JS中对象都会有个内置属性,即__proto__,(隐式原型链的属性),一般情况下执行创建它的构造函数的prototype的属性,另外函数比较特殊,也会有该属性

p1.__proto__ == Person.prototype

JS 引擎查找摸个属性时,先查找对象本身是否存在该属性,如果不存在就会在原型链上一层一层进行查找

有几个面试经常会问的几个问题
如何精确地判断短数组的类型

[] instanceof Array   //[].__proto__ == Array.prototypeObject.prototype.toString.call([])  //[Object Array]Array.isArray([]) //true[].constructor ==Array

下面代码输出什么

Object instanceof Function //true
Function instanceof Object // true

实现一个原型链继承

 function Person(name){this.name = name}Person.prototype.sayHello(){console.log('sayHello')}function Boy(){};Boy.prototype = new Person();let b1 = new Boy();b1.sayHello() //sayHello

原型、原型链、构造函数、实例的关系

1.instanceof检测构造函数与实例的关系:

 function Person () {.........}person = new Person ()res = person instanceof Personres  // true

2.实例继承原型上的定义的属性:

 function Person () {........}Person.prototype.type = 'object n'person = new Person ()res = person.typeres  // object n

3.实例访问 ===> 原型

实例通过__proto__访问到原型 person.proto=== Person.prototype

4.原型访问 ===> 构造函数

原型通过constructor属性访问构造函数 Person.prototype.constructor === Person

5.实例访问===>构造函数

person.proto.constructor === Person

二、原型链

在读取一个实例的属性的过程中,如果属性在该实例中没有找到,那么就会循着 proto 指定的原型上去寻找,如果还找不到,则寻找原型的原型:

实例上寻找

function Person() {}Person.prototype.type = "object name Person";person = new Person();person.type = "我是实例的自有属性";res = Reflect.ownKeys(person); //尝试获取到自有属性console.log(res);res = person.type;console.log(res); //我是实例的自有属性(通过原型链向上搜索优先搜索实例里的)

原型上寻找

function Person() {}Person.prototype.type = "object name Person";person = new Person();res = Reflect.ownKeys(person); //尝试获取到自有属性console.log(res);res = person.type;console.log(res); //object name Person

原型的原型上寻找

function Person() {}Person.prototype.type = "object name Person";function Child() {}Child.prototype = new Person();p = new Child();res = [p instanceof Object, p instanceof Person, p instanceof Child];console.log(res); //[true, true, true] p同时属于Object,Person, Childres = p.type; //层层搜索console.log(res); //object name Person (原型链上搜索)console.dir(Person);console.dir(Child);

原型链上搜索

  • 原型同样也可以通过 proto 访问到原型的原型,比方说这里有个构造函数 Child 然后“继承”前者的有一个构造函数 Person,然后 new Child 得到实例 p;

  • 当访问 p 中的一个非自有属性的时候,就会通过 proto 作为桥梁连接起来的一系列原型、原型的原型、原型的原型的原型直到 Object 构造函数为止;

  • 原型链搜索搜到 null 为止,搜不到那访问的这个属性就停止:

function Person() {}Person.prototype.type = "object name Person";function Child() {}Child.prototype = new Person();p = new Child();res = p.__proto__;console.log(res);         //Person {}res = p.__proto__.__proto__;console.log(res);         //Person {type:'object name Person'}res = p.__proto__.__proto__.__proto__;console.log(res);         //{.....}res = p.__proto__.__proto__.__proto__.__proto__;console.log(res);         //null

继承

  • JS 中一切皆对象(所有的数据类型都可以用对象来表示),必须有一种机制,把所有的对象联系起来,实现类似的“继承”机制。
  • 不同于大部分面向对象语言,ES6 之前并没有引入类(class)的概念,JS 并非通过类而是通过构造函数来创建实例,javascript中的继承是通过原型链来体现的。
  • 其基本思想是利用原型让一个引用类型继承另一个引用继承的属性和方法。

什么是继承

  • js中,继承是一种允许我们在已有类的基础上创建新类的机制;它可以使用现有类的所有功能,并在无需重新编写
  • 原来的类的情况下对这些功能进行扩展。

为什么要有继承

  • 提高代码的重用性、较少代码的冗余

目前我总结的一共有6种继承方式:

  • 原型链继承,
  • 借用构造函数继承,
  • 组合式继承(原型链+构造函数),
  • 原型式继承,
  • 寄生式继承,
  • 寄生组合式继承
function Person(name){this.name = name;this.sum=function(){alert('this.name',this.name)}
}
Person.prototype.age = 100

1.原型链继承

  • 实现方式: 利用原型链的特点继承,让实例的原型等于父类的实例

  • 优点: 实例可以继承父类的构造个函数,实例的构造函数,父类的原型

  • 缺点: 不能向父类传递参数,由于实例的原型等于父类的实例,那么改变父类的属性,实例的属性也会跟着改变

function child(){this.name="xiaoming"
}
child.prototype = new Person()
let child1 = new Child()
child1.name //xiaoming
child1.age //100
child1 instanceof Person //true

2.借用构造函数继承

  • 实现方式: 使用call/apply将父类的构造函数引入子类函数

  • 优点: 可以祢补原型链继承的缺点,可以向父类传递参数,只继承父类构造函数的属性

  • 缺点: 不能复用,每次使用需要重新调用,每个实例都是父类构造函数的副本,比较臃肿

 function child(){Person.call(this,'xiaoming')}let child1 = new child()child1.name //xiaomingchild1.age //100child1 instanceof Person //false

3.组合式继承

  • 实现方式: 复用+可传递参数

  • 优点: 基于原型链的优点和借用构造函数的优点

  • 缺点: 调用两遍父类函数

 function child(){Person.call(this,'xiaoming')}child.prototype = new Person let child1 = new child()child1.name //xiaomingchild1.age //100child1 instanceof Person //truechild instanceof Person //false

4.原型式继承

  • 实现方式: 函数包装对象,返回对象的引用,这个函数就变成可以随时添加实例或者对象,Object.create()就是这个原理

  • 优点: 复用一个对象用函数包装

  • 缺点: 所有实例都继承在原型上面 无法复用

 function child(obj){function F(){}F.prototype = objreturn new F()}let child1 = new Person()let child2 = child(child1)child2.age //100

5.寄生式继承

  • 实现方式: 在原型式继承外面包了一个壳子

  • 优点: 创建一个新对象

  • 缺点: 没有用到实例 无法复用

 function child(obj){function F(){}F.prototype = objreturn new F()}let child1 = new Person()function subObject(){let sub =child(child1)sub.name='xiaoming'return sub}let child2 = subObject(child1)typeof subObject //functiontypeof child2 //objectchild2.age //100

6.寄生组合式继承

  • 实现方式: 在函数内返回对象的调用

  • 优点: 函数的实例等于另外的一个实例,使用call/apply引入另一个构造函数,可传递参数,修复了组合继承的问题

  • 缺点: 无法复用

 function child(obj){function F(){}F.prototype = objreturn new F()}let child1 = child(Person.prototype)function Sub(){Person.call(this)}Sub.prototype = childchild.constructor = Sublet sub1 = new Sub()sub1.age //100

详细理解JS的三座大山相关推荐

  1. 详细理解JS中的继承

    正式说继承之前,有两个相关小点: JS只支持实现继承,即继承实际的方法,不支持接口继承(即继承方法的签名,但JS中函数没签名) 所有对象都继承了Object.prototype上的属性和方法. 说继承 ...

  2. 彻底理解js中this

    相关博文:http://blog.csdn.net/libin_1/article/details/49996815 彻底理解js中this的指向,不必硬背. 首先必须要说的是,this的指向在函数定 ...

  3. 【学习笔记】深入理解js原型和闭包(11)——执行上下文栈

    继续上文的内容. 执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境.当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境.处于活动状态的执行 ...

  4. 理解JS的6种继承方式

    [转]重新理解JS的6种继承方式 写在前面 一直不喜欢JS的OOP,在学习阶段好像也用不到,总觉得JS的OOP不伦不类的,可能是因为先接触了Java,所以对JS的OO部分有些抵触. 偏见归偏见,既然面 ...

  5. 彻底理解 JS Event Loop(浏览器环境)

    最近罗列了一些软件开发基础知识点,计划逐一的.彻底的理解每一个知识点,并为每个知识点写一篇详细的,图文并茂的文章.这篇是关于浏览器环境下 JS 的 Event Loop 机制(如有错误,欢迎指出). ...

  6. 深入理解js的执行机制

    写在前面 javascript在浏览器中被浏览器的js引擎执行解释,从执行上下文的角度分析一下js的执行机制 执行上下文 执行上下文被定义成javascript引擎在处理理解js代码时,所创建的一个动 ...

  7. 【学习笔记】深入理解js原型和闭包(5)——instanceof

    又介绍一个老朋友--instanceof. 对于值类型,你可以通过typeof判断,string/number/boolean都很清楚,但是typeof在判断到引用类型的时候,返回值只有object/ ...

  8. 简单理解js闭包、类型引用....第一章

    js 闭包函数.类型引用.this指向.对象原型链...这些东西让我们对js又爱又恨!js虐我千百遍,我待js如初恋. 很多初学者一开始会觉得这些概念没什么用,导致对这些东西产生一种抵抗力.接下来我们 ...

  9. 理解js中this的指向

    彻底理解js中this的指向 JavaScript 的 this 指向问题深度解析 转载于:https://www.cnblogs.com/jeacy/p/6509616.html

最新文章

  1. Python爬取近10万条程序员招聘数据,告诉你哪类人才和技能最受热捧!
  2. c语言程序中的基本功能,c语言程序中的基本功能模块为什么?
  3. POJ2296二分2sat
  4. 设置Linux下Mysql表名不区分大小写
  5. MyEclipse 10(汉化版)安装教程
  6. 在Ajax方式产生的浮动框中,点击选项包含某个关键字的选项
  7. 索引sql server_SQL Server索引设计基础和准则
  8. Tomcat服务与搭建(一)
  9. android 查找所有的串口,Android 串口通讯 获取卡号
  10. python机器学习案例系列教程——逻辑分类/逻辑回归LR/一般线性回归(softmax回归)
  11. MyEclipse2014+JDK1.7+Tomcat8.0+Maven3.2 开发环境搭建
  12. en55032最新标准下载_欧盟新EMC标准EN55032
  13. fu7推挽胆机音质_fu7电子管功放电路图大全(6N8P\6P3P\胆机功放电路\耦合电容器) - 全文...
  14. shadowdocksc错误;端口已被占用
  15. java excel 边框颜色_java(jxl) 改变excel表格背景及边框
  16. Bee 事务注解 @Tran 使用实例
  17. Vue项目中使用Tinymce
  18. w3c html5 ajax,Ajax教程学习笔记(W3CSchool)
  19. android 魔力锁屏,锁屏软件横评:锁屏功能PK_Android软件合辑_软件合辑_太平洋电脑网PConline...
  20. 2021年中国博客软件市场趋势报告、技术动态创新及2027年市场预测

热门文章

  1. 2021个人年度总结
  2. Linux 只查找两层备份目录文件大小
  3. Python程序设计基础教学大纲
  4. are exo exo是什么歌 we_We Are One EXO!
  5. 自然语言处理之中文文本分析(jieba分词、词袋doc2bow、TFIDF文本挖掘)
  6. 计算机的主板显卡内存条怎么查,电脑显卡在哪看?查看自己电脑显卡的显存等信息的方法...
  7. 关于长高问题 我的看法
  8. 如何制作一个简单的游戏 Cocos2d-x 2.0.4
  9. 汽车电子技术——软考中级之系统集成项目管理工程师 高分(63+69)通过备考经验分享
  10. Java代码安装maven jar_Java中Maven项目导出jar包配置的示例代码