以下代码下载python后怎么运行代码的结果是()()(要求写出预解析过程)

1、函数的实参与形参length

可以看到,函数的length似乎返回了参数的个数,那么对于形参和实参有没有区别呢?答案是有。

可以看到,在函数中,用arguments.length取到的是函数的实际参数的个数。

另外,我们要知道var length = 10 这样写是不行的,因为length是JavaScript内置的属性,不能用作变量名或函数名。戳这里查看JavaScript有哪些保留关键字、内置属性等。

2、函数的解析与预解析过程(变量提升)

这道题还是挺吊炸天的。。我也想了半天。。下面我来讲一下,涉及到函数的解析和预解析过程。

fn这样的函数声明,会进行函数预解析这么个过程,什么是函数预解析?通俗的说就是,从函数体里找变量和函数声明的过程,找到的变量(遇到var就找到了变量)不会去读具体的值,只会赋为undefined;找到的函数声明会赋值为整个函数体,这里有个知识点就是,如果找到的变量和声明同名,那么声明会覆盖变量(我的理解是,毕竟函数体比undefined的强嘛)。

比如此例中,预解析时找到了变量a,并且赋值为undefined,找到了声明function a(){alert(1)},为整个函数体;两者同名,所以声明覆盖了变量a的值,a不再是undefined的,而是函数体。

预解析完成后调用了方法,开始一步一步走方法。首先console.log(a),这时打印出的是函数体;接着var a = 2,a的值从函数体被改成了 2 ;接着是个function a(){}函数声明,注意,声明不能改变变量的值,所以走完这一句,a的值还是2,接着打印出了2。

有人肯定有这样的疑惑,为什么a=1传进去没起作用呢?这里有一个原则,就是局部变量优先,基于这个原则,我们再来分析一下a的变化过程。预解析中,a=undefined,a=function(){alert(1)},此时参数有值等于1,本应该将a赋值为1,但却没有,原因是此时的a已经等于局部函数声明function(){alert(1)},所以外部传进来的参数1并没有取代a的值;假如本例没有function(){alert(1)}这一句,打印出的将是1,

局部变量优先原则,原理同下:

  console.log(a)  // 10,局部变量优先,在局部找到a后,不会再向外查找

3、变量提升、window的变量

首先,if(){}的花括号并不像function(){}的花括号一样,具有自己的块级作用域,if的花括号还是全局的环境。根据JavaScript的变量提升机制,var a会被js引擎解释到第一行,如下:

这道题我在做的时候踩了个坑,我在代码编辑器里写了如下代码:

这时候,a这个变量是定义在匿名函数function(){}里的,属于该函数的局部变量,所以a不再是window的对象。大家一定要注意细节。

变量a与s都是基本类型,无法给他们添加属性,所以a.pro和s.pro都是undefined。

如果实在想给字符串添加属性,我们需要将字符串定义为对象类型的字符串,如下:

async 表示这是一个async函数,await只能用在这个函数里面。

await 表示在这里等待promise返回结果了,再继续执行。

首先打出hello,到了await,会等待promise的返回,所以“world”不会立刻打出,接着进入sleep函数,打出666,接着开了一个1秒的定时器,虽然js是单线程的,但setTimeout是异步的,在浏览器中,异步操作都是被加入到一个称为“events loop”队列的地方,浏览器只会在所有同步代码执行完成之后采取循环读取的方式执行这里面的代码,所以resolve被加入任务队列,先打印了888,一秒后执行了resolve,表示promise成功返回,打出了world。

以上每道题都是本渣自己的想法和理解,如有不正确的地方烦请读者指正,大佬轻喷~

刚入职半个月,无缝对接的加入了新公司,完美。抽空整理了下最近遇到的面试题,面试公司包括:阿里,腾讯,美的,顺丰,平安金服。base是深圳,后三个都比较顺利拿到了offer,离大厂还有点差距,加油鸭~
因为是初级前端,面试题可能过于基础,有错误望指出?。
有些问题当场没有很全面的答出,后续上网查资料补齐了,可能会有很多雷同,哈哈
有很多题目在这个网站能看到,强推一波:";
 


//o是形参,对象的引用,依旧指向原地址,相当于 var o = webSite;赋值改变对象的属性 //变量o指向新的地址 以后的变动和旧地址无关,题目打印的是外部";


 
 
 
 
 

11、 一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?

 
 
1、浏览器会开启一个线程来处理这个请求,对 URL 分析判断如果是 http 协议就按照 Web 方式来处理;
2、调用浏览器内核中的对应方法,比如 WebView 中的 loadUrl 方法;
3、通过DNS解析获取网址的IP地址,设置 UA 等信息发出第二个GET请求;
4、进行HTTP协议会话,客户端发送报头(请求报头);
7、处理结束回馈报头,此处如果浏览器访问过,缓存上有对应资源,会与服务器最后修改时间对比,一致则返回304;
8、浏览器开始下载html文档(响应报头,状态码200),同时使用缓存;
9、文档树建立,根据标记请求所需指定MIME类型的文件(比如css、js),同时设置了cookie;
10、页面开始渲染DOM,JS根据DOM API操作DOM,执行事件绑定等,页面显示完成。
 

12、ajax实现原理及方法使用

 
原生ajax的请求步骤 //规定请求的类型、URL 以及是否异步处理请求。 //发送信息至服务器时内容编码类型 //接受服务器响应数据
 
便于diff算法的更新,key的唯一性,能让算法更快的找到需要更新的dom,需要注意的是,key要唯一,不然会出现很隐蔽性的更新问题。
 

14、vue双向绑定的原理是什么?

 
双向数据绑定是基于Object.defineProperty()重新定义get和set方法实现的。修改触发set方法赋值,获取触发get方法取值,并通过数据劫持发布信息。
 
 
处理数据动态变化后,dom还未及时更新的问题。$nextTick就可以获取到数据更新后最新的dom变化
 

16、浅谈前端工程化、模块化、组件化

 
 
1、将前端项目当成一项系统工程进行分析、组织和构建从而达到项目结构清晰、分工明确、团队配合默契、开发效率提高的目的
 
 
1、可以简单的认为模块化和组件化是工程化的表现形式
 
es6带来了语言原生的模块化方案:

 
 
 
a、直接使用sort()方法,默认的排序方法会将数组元素转换为字符串,然后比较字符串中字符的UTF-16编码顺序来进行排序。
b、sort,可以接收一个函数,返回值是比较两个数的相对顺序的值
 

31、箭头函数与普通函数的区别

 
a、箭头函数是匿名函数,不能作为构造函数,不能使用new
b、箭头函数不绑定arguments,取而代之用rest参数...解决
c、箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
d、箭头函数通过call()或apply()方法调用一个函数时,只传入了一个参数,对 this 并没有影响。
e、箭头函数没有原型属性
// 引入方式 1 对应导出方式 1
// 引入方式2 对应导出方式 2
 
 

25、什么是虚拟dom,优势是什么,存储在哪

 
1、具备跨平台的优势-由于 Virtual DOM 是以 JavaScript 对象为基础而不依赖真实平台环境,所以使它具有了跨平台的能力,比如说浏览器平台、Weex、Node 等。
2、提升渲染性能-Virtual DOM的优势不在于单次的操作,而是在大量、频繁的数据更新下,能够对视图进行合理、高效的更新。
为了实现高效的DOM操作,一套高效的虚拟DOM diff算法显得很有必要。通过找出本次DOM需要更新的节点来更新,其他的不更新。
3、是一个js对象,存储在内存中。
 
 
1、Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
2、Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
3、Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
4、Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
5、Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。
6、Output:打包后文件输出的位置。
 
 
 
1、客户端使用https的url访问web服务器,要求与服务器建立ssl连接
2、web服务器收到客户端请求后, 会将网站的证书(包含公钥)传送一份给客户端
3、客户端收到网站证书后会检查证书的颁发机构以及过期时间, 如果没有问题就随机产生一个秘钥
4、客户端利用公钥将会话秘钥加密, 并传送给服务端, 服务端利用自己的私钥解密出会话秘钥
5、之后服务器与客户端使用秘钥加密传输
 

29、如何防范CSRF攻击,XSS攻击

 
2、输入检查-不要相信用户的所有输入
3、输出检查-存的时候转义或者编码
 
1、组件化将页面视为一个容器,页面上各个独立部分例如:头部、导航、焦点图、侧边栏、底部等视为独立组件,不同的页面根据内容的需要,去盛放相关组件即可组成完整的页面。
2、模块化和组件化一个最直接的好处就是复用
 
 
1、@import是 CSS 提供的语法规则,只有导入样式表的作用;link是HTML提供的标签,不仅可以加载 CSS 文件,还可以定义 RSS、rel 连接属性等。
2、加载页面时,link引入的CSS被同时加载,@import引入的CSS将在页面加载完毕后加载。
3、link标签作为HTML元素,不存在兼容性问题,而@import是CSS2.1才有的语法,故老版本浏览器(IE5之前)不能识别。
4、可以通过JS操作DOM,来插入link标签改变样式;由于DOM方法是基于文档的,无法使用@import方式插入样式。
 

18、请写一个正则15-20位的大写字母或数字

 

19、如下代码输出是什么

 

20、如下代码输出是什么

 

21、如下代码输出的是什么

 
setTimeout 和 setInterval的运行机制是将指定的代码移出本次执行,等到下一轮 Event Loop 时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮 Event Loop时重新判断。
这意味着,setTimeout指定的代码,必须等到本次执行的所有同步代码都执行完,才会执行。
new Promise是立即执行,先打印2,3,然后5,再执行then打印4,最后是1
 
 
2、在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢。

22、vue项目优化的手段有哪些

 
2、图片,资源放cdn
3、页面图片较多进行懒加载
 
 
 
模型(Model):数据保存
视图(View):用户界面。
 


Model 将新的数据发送到 View,用户得到反馈
 
 
1. 各部分之间的通信,都是双向的。
3. View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。
 
 
View的变动,自动反映在 ViewModel,反之亦然。
 
 
 
1、需要频繁操作dom
2、容易引起重绘和回流,影响页面性能
 
 
1、mvvm模式,采用虚拟dom不需要频繁操作dom,通过双向绑定,用数据驱动页面变化,页面变化对应数据也发生变化,只需要关注数据层的业务逻辑,而无需关注视图层的更新。可以尽量减少无用的更新操作,提高dom渲染效率。
2、组件化开发,页面由若干个组建组成,可复用性高。
3、社区环境好,各类资源文档十分齐全。
4、通过Object.defineProperty() 方法,监控对数据的操作,从而可以自动触发数据同步。
 
2、一切都是组件,组件实例之间可以嵌套。
3、使用独特的jsx语法。

该文章是为大家整理一个关于js的知识网络,重点是知识的罗列及之间的联系,所以实例可能会有所不足,导致可能没有对应知识的人看不懂,希望大家能够结合其他资料来学习这篇文章,并整理出自己的知识体系。

JavaScript是解释型语言,这就是说它无需编译,直接由JavaScript引擎直接执行。

既然说到了解释型语言,那么我们就来分别以下解释型语言和编译型语言的差别:

  • 编译型语言:程序在执行之前需要一个专门的编译过程,把程序编译成为机器语言的文件(即exe文件),运行时不需要重新编译,直接用编译后的文件(exe文件)就行了。
  • 解释型语言:程序不需要编译,程序在运行的过程中才用解释器编译成机器语言,边编译边执行(没有exe文件)。

其中程序无需编译,不是说真的不需要编译了,直接执行脚本字符串。而是说不需要在运行之前先编译程序成为exe文件,而是在运行的过程中边运行边执行。

ok,我们回到JavaScript的解析执行过程。

在整体上,JavaScript的解析执行过程分为两个步骤:

其中,编译是在解释器中进行,将代码编译成可执行码。运行是在JavaScript引擎中进行,执行可执行码。

编译过程不必多说,我们只要清楚这个过程会将字符串代码编译为可执行码。

重点是运行过程,运行又由两个过程组成

重点注意收集变量这一功能,又名为变量提升,收集的变量有以下三种:

  1. arguments参数,值为传入的实参

若是变量名有重复的话,按照优先级来确定:

  1. let和const声明的变量不会在预解析阶段变量提升,只有在执行阶段执行到该行时才会声明该变量
  2. 当我们给一个未声明的变量赋值时,JavaScript引擎会认为我们是要声明一个全局变量。但如果我们访问一个为声明的全局变量,会报错

JS执行是需要分号的,但为什么以下语句却可以正常运行呢?

正是因为预解析阶段会进行分号补全操作。

列举几条自动加分号的规则:

  • 当有换行符(包括含有换行符的多行注释),并且下一个token没法跟前面的语法匹配时,会自动补分号。
  • 当有}时,如果缺少分号,会补分号。
  • 程序源代码结束时,如果缺少分号,会补分号。

不过若是以下的情况,必须得加上';',否则的话,会出现报错。

  • 如果一条语句以"(","{","/","+","-"开始,当前一条语句没有用;结尾的话,就会与前一条语句合在一起解释

还有,其实所有代码都可以写在一行中。只要有';'来分隔开每一句就ok。并且,if及for及while的函数体也可以写在同一行中。

只要做好分隔工作,那么就都可以写在同一行。

  1. 栈内存的数据结构为栈,堆内存的数据结构为树

关于不同类型的数据是如何存储在内存的,参考下图:

需要特别注意的是,如下:

变量a存储的值不是该对象,而是该对象在堆内存中的地址。

 
 

在变量对象中的数据发生复制行为时,系统会自动为新的变量分配一个新值。var b = a执行之后,a与b虽然值都等于20,但是他们其实已经是相互独立互不影响的值了。具体如图。所以我们修改了b的值以后,a的值并不会发生变化。

在demo02中,我们通过var n = m执行一次复制引用类型的操作。引用类型的复制同样也会为新的变量自动分配一个新的值保存在变量对象中,但不同的是,这个新的值,仅仅只是引用类型的一个地址指针。当地址指针相同时,尽管他们相互独立,但是在变量对象中访问到的具体对象实际上是同一个。如图所示。

因此当我改变n时,m也发生了变化。这就是引用类型的特性。

内存空间管理及垃圾回收机制

内存是有限的,所以分配的内存必须得在适当的时机回收以供后继使用。

  1. 为变量分配所需要的内存空间

这第三步对应的就是垃圾回收。

那么JavaScript引擎是如何判断该内存需不需要释放呢——标记清楚机制。

垃圾回收器每隔一段时间都会检查一次内存,找到其中失去引用的变量,并释放掉。

其中失去引用一般有两种原因

  1. 函数执行完,局部变量没有存在的必要
  2. 没有变量指向堆内存的对象
  1. 减少使用全局变量,因为全局变量对应着主函数,除非关闭该页面,否则主函数一直不会弹出调用栈,使得全局变量在关闭页面前一直不会被释放

浅拷贝开辟一个新的内存空间,仅拷贝第一层对象内容,深拷贝也开辟一个新的内存空间,拷贝所有层对象堆内容。

这就是最常见的,但还不是浅拷贝,那如果我们这样呢?

这样输出的是false,也就是说arr1和arr2指向的地址不同。那么这样就是深拷贝了吗?

不是的。如果arr1的成员中有个对象呢?那么对该对象的复制就是浅拷贝。

那么究竟如何才能做到浅拷贝呢?使用递归,每一次递归进行一次如上的拷贝,直到当前层递归数据为非对象。

无需底层实现的浅拷贝与深拷贝:

浅拷贝:(以下方法仅适用于数组的浅拷贝)

深拷贝:(这个方法既适用于对象又适用于数组)

使用typeof来检测基本类型,用instanceof来检测对象还是数组

typeof一般只能返回如下结果:

由于引用类型的数据用typeof返回的都是object(除function),所以我们用instanceof来判断究竟是什么引用类型(这个说法不是很严谨,大家可以不要记忆这个概念)。

instanceof的使用一般是左值为对象,右值为构造函数。

沿着左值对象的__proto__这条线走,并且沿着右值构造函数的prototype这条线走,只要两者能够交叉,即同一个对象,那么就返回true。如果__proto__这条线已经走到头了,还未交叉,则返回false。

所以说,与其说instanceof判断的是什么引用类型,倒不如说是判断是否有继承关系。

大家应该都有接触过函数调用栈吧,执行上下文就是每次压入栈的内容。

执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。

JavaScript的执行环境大致可以分为三种:

  1. 全局执行环境:JavaScript代码运行起来会首先进入该环境
  2. 函数执行环境:当函数被调用时,会进入当前函数的环境
  3. eval(不建议使用,忽略)

所以,JavaScript只有全局作用域及函数作用域。

所以,我们可以这样理解——当开始执行JavaScript代码时,会创建一个全局上下文。每当执行一个函数,就会创建一个函数执行上下文。

JavaScript引擎会以栈的方式处理它们,这个栈我们称为函数调用栈。栈底永远是全局上下文,栈顶就是当前正在执行的执行上下文。

所以,统一一下——当开始执行JavaScript代码时,创建一个全局上下文,压入函数调用栈。每当执行一个函数,就会创建一个函数执行上下文,压入函数调用栈。当函数执行完,该执行上下文弹出栈。直到关闭该页面,才会弹出全局上下文。

  1. 在此,我可以把JavaScript代码解析执行的过程细化一下
    • 当开始执行JavaScript代码时,会编译全局作用域的代码,然后预解析其可执行码(变量提升,分号补全),然后执行。当执行完后,再次编译函数作用域的代码,然后预解析其可执行码(变量提升,分号补全),然后执行。以此类推,直至执行完JavaScript代码。
    • 这才是JavaScript代码边编译边运行的具体过程,而不是编译一行,执行一行

执行上下文的生命周期可以分为两个阶段:

    • 在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,以及确定this的指向。
    • 创建完成之后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。

执行上下文由三部分组成:

该对象存储的就是变量提升的arguments参数,var声明的变量,函数声明。

在未进入执行阶段时,变量对象(VO variable Object)中的属性都不能访问。但在进入执行阶段时,变量对象转换为了活动对象(AO active Object),里面的属性都能被访问。

VO和AO其实都是一个对象,只是处于执行上下文的不同生命周期。只有在函数调用栈的顶部执行上下文的变量对象才会变成变量对象。

由该环境和所有父环境的变量对象组成的链式结构,保证了当前执行环境对符合访问权限的变量和函数的有序访问

我们通过作用域链,遍历自身的变量对象到全局对象,直到找到对应的变量。

理解作用域链非常关键,这是理解闭包的基础。

执行上下文和作用域是两个完全不同的概念。作用域是在编译阶段就确定下来的,执行上下文是在执行阶段才能够创建的。

不过,切记,当前作用域和上层作用域不是包含关系。

关于垃圾回收机制,有一个重要的行为,那就是,当一个值,在内存中失去引用时,垃圾回收机制会根据特殊的算法找到它,并将其回收,释放内存。

而我们知道,函数的执行上下文,在执行完毕之后,生命周期结束,那么该函数的执行上下文就会失去引用。其占用的内存空间很快就会被垃圾回收器释放。可是闭包的存在,会阻止这一过程。

  1. 函数A嵌套在函数B以内

闭包的核心就是——通过在外部函数(B)的外部(C)保存内部函数(A)的引用,当执行该引用(A)时,由于创建的执行上下文的作用域链中包含有外部函数(B)的引用,从而使外部函数(B)的执行上下文不会被垃圾回收。

  • 函数C中声明函B,函数B中声明函数C

这样就能保存之前执行函数B的操作结果。这样的话,就可以在其他的执行上下文中,操作到函数B的操作结果。

要切记哦:虽然函数A被保存在了函数C中,但函数A的作用域链并没有变化,千万不要把作用域链和函数调用栈混在一起了。在闭包中,能访问到的仍然是作用域链上能查询到的数据。

闭包返回的作用域链中,中间层及之前层的都是不变的内存区域,只有最高层的变量对象是每次调用函数的时候新创建的变量对象。

  1. 这些变量的值保存在内存里,不会在外层函数调用后自动删除
  1. 如若闭包为全局变量,会造成内存泄漏

关于this的指向一直是大家比较头疼的地方,似乎很难找到一个确切的标准。但this的指向还是有标准的,且往下看。

this的执行是在调用函数,即执行上下文创建时才能确定的,判断标准如下:

  1. new绑定:this指向新创建的对象
  2. 显式绑定:this指向调用该函数的对象
  3. 默认绑定:非严格模式下,this指向全局对象,严格模式,this为undefined
    • 全局代码中的this永远指向全局变量

new到底做了什么呢?

  1. 将构造函数的this指向该对象
  2. 执行构造函数,从而能够使用构造函数中的this为该新对象添加属性

这两者的作用都是修改函数中的this指向,功能一直,只是参数的写法有稍许不同。

call传参需要一个一个地传,而apply传参是传一个数组。

call,apply会直接执行。bind是在函数调用之前,改变this的指向,它会返回一个函数。

箭头函数是ES6的新语法,形式如下:

其中,如果参数只有一个,则可以省略括号。如果没有参数或多个参数,括号不能省略。

箭头函数中this指向规则与普通函数的规则不同,他的this指向规则为:

捕获其所在(即定义的位置)上下文的this值, 作为自己的this值,

  1. 注意,在箭头函数中普通函数的this指向规则都失效了,也就无法使用apply,call,bind来改变函数中的this指向了。
  2. 箭头函数无法访问参数对象arguments
  3. 在对象中的方法千万不要用箭头函数来定义,因为该方法中的this不执行该对象

普通函数的this指向和setTimeout中的箭头函数的指向都是Person对象。

面向对象式编程,继承,原型链

JavaScript是一门面向过程的语言,但随着网页需求功能的复杂化,工程化,要求JavaScript应该也有面向对象编程的能力。

我们可以通过字面量对象来创建一个简单对象

当我们想要给我们创建的简单对象添加方法时,可以这样表示

访问属性的时候,可以用一下两种方式

当我们想要用一个变量值来作为属性名来访问属性,就用第二种方法。

使用上面的方式创建对象很简单,但是在很多时候并不能满足我们的需求。就以person对象为例。假如我们在实际开发中,不仅仅需要一个名字叫做TOM的person对象,同时还需要另外一个名为Jake的person对象,虽然他们有很多相似之处,但是我们不得不重复写两次。

显然,这样是很不合理的,当有太多的相似对象,编写代码会极为痛苦。

工厂模式就是你给出原料,然后返回给你产品。

不要把工厂模式想的太高大上。显然,工厂模式帮我们解决了重复编码的麻烦,但是他还有一个问题

无法识别工厂模式返回的对象的类型。(其次还有每次返回对象都得为方法分配一个新的内存空间,浪费资源)

如上述代码,Tom和Cherry指向的对象类型都是Object类型。

首先构造函数就是个普通的函数,其本身没有什么特别的地方。

但构造函数的特殊之处就在于用new创建一个对象,构造函数对该对象的属性进行添加。

new的具体过程在上文有详细提到,就不赘述了。

就这样,new关键字+构造函数就能够创建出一个有属性的对象,且还能够识别对象类型。

但是又有一个问题来了:

所有用该构造函数创建的对象访问的方法实现是一模一样的,但是每次new的时候都会在内存中分配一片新的空间以保存变量的特性和方法。

显然这是不合理的,既然访问的是同一个方法实现,那么为什么不能每个实例对象都访问同一块内存里的方法呢?

我们创建的每一个函数,都有prototype属性指向原型对象,可以选择在原型对象里挂载属性和方法,这样每创建一个对象,都可以通过__proto__访问到原型对象,也就不需要再为这些属性和变量分配空间了。

由于每个函数都可以是构造函数,每个对象都可以是原型对象,因此如果在理解原型之初就想的太多太复杂的话,反而会阻碍你的理解,这里我们要学会先简化它们。就单纯的剖析这三者的关系。

通过图示我们可以看出,构造函数的prototype与所有实例对象的__proto__都指向原型对象。而原型对象的constructor指向构造函数。

构造函数中this添加的属性和方法是私有属性和方法(虽然这个私有属性和方法能够被外界直接取到),原型对象中的属性和方法是共有属性和方法。

当我们访问实例对象中的属性或者方法时,会优先访问实例对象自身的属性和方法,即私有属性和方法。如若找不到,则去原型对象中寻找。

每一个对象既可以作为原型对象,又可以作为实例对象,而且有可能既是实例对象又是全局对象,这样的一个对象正是原型链中的一个节点。

  1. 在子构造函数中复制父构造函数添加的属性和方法。即让父构造函数中的操作在子构造函数中重现一遍
  2. 子级的原型对象指向父构造函数的实例对象、

该继承方案有一个问题,就是

子级的原型对象是父构造函数的实例对象,这样的话,我们就调用了两次父级的构造函数(子级对象中的将子级原型对象中的给屏蔽了)。

子级原型对象为父级实例对象,实际目的仅是父级实例对象中的__proto__,从而形成原型链来寻找属性和方法。

重复调用两次父级的构造函数是没有意义的,所以我们改进一下代码。

这就是继承的最终解决方案了。

前面说过,js只有全局作用域及函数作用域,没有块级作用域。

但是let和const会引入块级作用域。

  1. 不会在预解析阶段变量提升,只有当执行到该行时才会创建let或const变量
  2. 不可重复命名,否则报错

我们常常使用let来声明一个值会被改变的变量,而使用const来声明一个值不会被改变的变量,也可以称之为常量。

以一个例子比对一下大家就知道了

 

使用``将整个字符串包起来,在其中使用${}包裹一个变量或表达式

同样,以一个例子来解释

 

是不是很简单,就是将访问属性与变量命名在写法上合并为一步。

另外,数组也有属于自己的解析结构

数组以序列号一一对应,这是一个有序的对应关系。
而对象根据属性名一一对应,这是一个无序的对应关系。

在ES6中用...来表示展开运算符,它可以将数组或者对象进行展开。

展开字符串还可以运用在参数中

对象字面量和class

ES6对对象字面量做了简化语法的处理。

  1. 当对象字面量的属性和值的变量同名时,可以省略值的声明
  2. 除了属性以外,对象字面量的方法也可以简写
  3. 在对象字面量中可以用[]作为属性名,表示属性名也可以用变量

class是ES6的新功能。JavaScript创建类的方式是构造函数,这对于普通的面向对象程序员来说太过别树一帜了。

所以,ES6模拟普通面向对象的类-对象编写代码方式,创造了class。

class实际上就是一个var声明变量,其指向其中的constructor构造函数,只是在原先的构造函数创建类的模式做了形式上的变形,使更符合JAVA的类-对象模式。

  1. 其中,constructor函数就是原先的构造函数,当new一个对象的时候就是默认调用这个函数,因为类名就是指向的constructor函数。

  2. 类中声明的方法,都是定义在该构造函数的prototype对象中。相当于说,只要是类中的方法,都是使用构造函数.prototype.方法名=...来声明。
    • 所以我们可以在声明了类之后,通过类名.prototype.方法名=...来在类外动态为类定义方法。
    • 同时,我们还可以用Object.assign(类名.prototype,字面量对象-就是一个对象,用{}来声明)。该字面量对象不会覆盖掉类中的其他方法,所做的工作是添加方法
  3. 如果你没有显式定义一个constructor,那么会隐式生成一个constructor方法,当new的时候调用,该方法返回this。由于new返回对象由构造函数及new执行,所以如果你想要返回的实例对象不是该类的实例,可以在里面return new A()
  4. class中只允许有方法,不允许声明变量,跟JAVA不一样
  5. 只有在类中constructor中用this指定的属性+方法才是实例属性+实例方法,在类中定义的方法是原型方法。然后在类外用那两种方法声明的属性+方法都是原型属性+原型方法
  6. 类的所有实例都共享一个原型对象,而同时类的所有实例又可以通过__proto__来修改原型对象内容,但千万别这样,因为这样会影响到别的该类的实例对象,不符合业务逻辑
  7. class类不会变量提升

  1. 在类中声明方法时,不要加上function,相当于强制使用对象字面量吧
  2. 在类中声明函数,函数之间不要用,隔离

然后在子的constructor函数中使用super()来调用父的构造函数,从而复制父的实例属性。继承过后,通过原型链,可以访问到父级的所有原型链(即可访问到以往所有被用prototype声明的对象)内容。

super在子类定义中有两种使用方法:

  1. 当做对象用,则是父的原型对象+类。所以父的实例属性是无法访问到的
  1. super作为对象使用时,调用父级的方法,super会绑定子类的this

静态方法的意义是:在该类所有实例之上,只属于类本身的方法,不是具体某个实例的方法,也不是所有实例都有的方法。

切记:静态方法的意义不是省空间(这是原型方法的意义,而且实例根据原型链访问不到静态方法),而是一个能够统领全局(中枢权)的方法。

由于静态方法中的this指向类本身,所以我们无法使用this来访问到实例属性,方法。但是,我们可以通过创建一个实例对象,从而访问到实例属性,方法。

关于私有,静态,原型,实例属性和方法,在类中get及set声明函数代码如下:

  1. class声明的类不能够不适用new就直接使用,但构造函数声明类的方式可以不适用new,但这样的话,该构造函数的this就是指向全局对象,从而直接调用构造函数时,往全局对象里增添属性,方法。
    • 我们在构造函数中使用new.target是否为false来判断是否使用new来调用该构造函数,从而规避刚刚的问题
  2. 静态方法中的this指向的是类本身,而不是实例。在实例创建前,静态属性,方法已经存在了。我们无法做到不创建一个对象就使用静态方法访问到实例属性
  3. This会慢慢叠加在一起,静态方法也会慢慢叠加,但原型不会叠加在一起
  4. 静态方法不能被实例对象调用,实例方法不能被类调用
  5. 父类的静态方法,可以被子类继承
  6. class类中不能够声明变量,只能声明方法
  7. 静态字段得等到程序关闭了才能释放。静态的要比非静态的先加载
  8. ES6声明的原型方法不能枚举,但用ES5的构造函数原型方法能够枚举
  9. 类名.name能够得到该类名称
  10. 在ES6中能够使用变量来作为属性名,[变量]:值,当要调用这个属性时,对象[变量]
  11. get,set配对声明,相当于声明一个完整的属性,相当于拦截该属性的存取行为,可用此实现一个私有变量
  12. 也可以在构造函数上声明静态属性和方法
  13. 原型对象是一个空的对象,当声明类的时候就创建出来的,由类来定义该对象内容,并将其prototype指向另一个原型对象(所以原型对象是由该类创建的,而不是由父类创建的)(修改prototype其实是打断原来的原型链,重建一个新的原型链)
对象中声明方法与class中声明方法的差别

我们要保证异步的顺序执行,在以往都是通过回调函数来实现。

然而回调函数有两个缺陷:

  1. 回调里面套回调,会导致“回调地狱”
  2. 如果使用回调的话,数据请求与数据处理没有分开来

这两者都会导致阅读源码的困难。

所以,我们需要新的方案能够有以下两个特点:

  1. 用扁平式代码结构代替嵌套式代码结构
  2. 数据请求与数据处理泾渭分明地区分开来

Promise对象有三种状态:

  1. pending:等待中,或者进行中,表示没有得到结果
  2. resolved:已经完成,表示得到了我们想要的结果,可以继续往下执行
  3. rejected:也得到结果,但由于结果并非我们所愿,所以拒绝执行

这三种状态不受外界影响,而且状态只能是从pending到resolved或者pending到rejected。我们通过Promise构造函数中的第一个函数参数来处理状态改变。

  1. 每当new一个Promise对象时(包括return一个刚new出来的Promise对象),就会立即执行其中的参数函数(不需要异步执行该参数函数)

Promise对象中的then方法,可以接受到Promise对象的状态变化。then方法有两个参数,第一个参数接受resolved状态的执行,第二个参数接受rejected状态与异常状态的执行。

又因为then方法执行完后会返回一个新建的Promise对象,所以可以继续用then来接受Promise对象的状态变化,并执行相应函数。这也是扁平式代码结构的核心。

Promise对象的rejected状态与异常状态具有冒泡性质,一直向后传递,直到被捕获为止。

  1. then中第一个参数函数的参数是前面Promise对象resolve()执行中的参数值
  2. then中第二个参数函数的参数和catch的参数函数的参数是前面Promise对象reject()执行中的参数值
  3. then方法执行完会返回一个Promise对象,如若没有异常,该Promise对象会执行resolve(),其中的参数是then方法中return的值。如果有异常,则该Promise对象会执行reject(),其中的参数是then方法中return的值

then与catch的微任务队列异步机制

new一个Promise对象时是立即执行其中的执行函数,但是then方法与catch方法中的参数函数是异步执行的。

即运行代码的过程中遇到then方法和catch方法,需要挂起其对应的参数函数,直到执行栈为空,才执行微任务队列中的任务。(顺带提一句,直至当前的微任务队列清空,下一个宏任务才能进栈)

切记,参数为一个数组,都由Promise对象组成。且传递给then的参数是所有Promise对象resolve的值组成的数组。如果参数中有一个Promise对象失败的话,则将第一个失败的reject值传递给catch。

同样的,参数是一个由Promise对象组成的数组,传递给then的值是第一个完成的Promise中resolve的值。

但是,Promise这个异步方案还不够完美,我们异步的最终目标就是以同步的书写方式书写异步。

酷吧!就是说我们并不关心他是不是异步,不管是同步还是异步我们都以从上到下的书写方式来书写代码,这才是人类的思维方式啊!

所以也就产生了async这个异步解决方案了。

需要注意的是,async是基于Promise的,即他不是一套独立的解决方案,而是基于Promise的异步机制的一次进步,代替then()的写法。

先声明几个重要的知识点:

  1. 每个async函数执行后返回的都是Promise对象,即每当执行一个async函数都会创建一个Promise对象并返回
  2. await的意义为等待其对应的Promise对象的异步任务执行完,否则将接下来的异步任务挂起,跳出async函数,执行函数外的代码。
  3. await关键字只能在async函数中使用
  4. 将async函数中的return值赋值给外部变量,可以做到异步任务之间的通信

  1. 进入主代码,输出'async start',并将setTimeout的回调函数放入宏任务队列中
  2. 遇到await关键字,其后是执行async函数async2,输出'async2',同时返回一个新建的Promise对象,由于没有async2函数执行时没有出现异常,所以这个Promise对象中的执行函数执行resolve(),并将该Promise对象then方法放入微任务栈中
  3. 跳出async1函数。这一步我单独拿出来,是为了说明其特殊性。若是没有await关键字的话,那么不会退出async1函数,而是继续执行函数,输出'async1 end'。await能够保证下一步的代码是在上一步的所有异步任务执行完后才执行的。这也就能够保证每个Promise对象之间又是同步的
  4. 遇到了new一个Promi对象,立即执行其执行函数,输出'promise1',并且将then函数放入微任务队列中
  5. 接下来到了重头戏,这个时候执行栈为空了,我们将微任务栈头取出放入执行栈中,即是第3步中的then方法,执行。没有输出,然后将await async2()之后的代码作为一个微任务放入微任务堆栈中。(这点也要特别注意,并不是直接同步执行下去,而是将接下来的代码作为一个微任务放入微任务队列)
  6. 将第5步中放入微任务队列中then放入执行栈执行,输出'promise2'
  7. 将第7步放入微任务队列的任务放入执行栈执行,输出'async1 end'
  8. 微任务队列清空,取出宏任务队列中的任务放入执行栈中执行,输出'setTimeout'
  1. 切记,如果await后面跟的是new一个Promise对象的话,Promise对象中的执行函数一定要执行resolve()或reject(),否则直至退出程序都不会执行该async函数后面的代码

我要回帖

更多关于 下载python后怎么运行代码 的文章

 

随机推荐