前端知识点总结之JavaScript

452 阅读16分钟

JS的七种数据类型?

1. Undefined

Undefined类型表示未定义,它的类型只有一个值,就是undefined。任何变量在赋值之前都是Undefined类型,值为undefined。一般我们可以用全局变量undefined(就是名为undefined的这个变量)来表达这个值,或者void运算来把任意一个表达式便成undefined值。在js中undefined是一个变量而非关键字,所以为了避免被篡改,建议使用void 0获取undefined值。

2. Null

Null类型表示定义了但为空,所以编程中我们不会把变量定义为undefined,这样保证所有值为undefined的变量都是从未赋值的状态。Null类型也只有一个值,就是null,它的语义表示空值。null是js关键字,所以可以放心用null关键字来获取null值。

3. Boolean

Boolean类型有两个值,true和false。

4. String

编码格式:blog.csdn.net/hongsong673…

String用于表示文本数据。String的意义并非‘字符串’,而是字符串的UTF16编码,我们的字符串操作charAt,length等都是针对UTF16编码。字符串的最大长度是2^53-1,但是这个长度单位是unicode码点(一个UTF16单元),所以字符串的实际最大长度是受字符串的编码长度影响的,当处理超出U+0000-U+FFFF范围的字符时要格外小心。

JS中的字符串一旦构造出来,无法用任何方式改变字符串的内容,所以字符串具有值类型的特征。

5. Number

JS中的Number类型有2^64-2^53+3个值。除了IEEE754-2008规定的双精度浮点数规定,JS为了表达几个额外的场景规定了几个例外情况:NaN,Infinity,-Infinity。根据双精度浮点数的定义,Number类型无法精确表示-0x1fffffffffffff-0x1fffffffffffff范围之外的整数,同样根据浮点数的定义,非整数的Number类型无法用===来比较(Number类型运行要先转化成二进制,将二进制运算,再转化成十进制。因为Number是64位双精度,小数部分只有52位,但0.1转化成二进制是无限循环的,所以四舍五入就发生了精度丢失,所以0.1+0.2==0.3为false)。

6. Symbol

Symbol是ES6引入的新类型,它是一切非字符串的对象key的集合,在ES6规范中,整个对象系统被用Symbol重塑。

7. Object

在 JavaScript 中,对象的定义是“属性的集合”。属性分为数据属性和访问器属性,二者都是 key-value 结构,key 可以是字符串或者 Symbol 类型。

类型转换:V8是怎么实现1+“2”的?

在 JavaScript 中,数字和字符串相加会返回一个新的字符串,这是因为 JavaScript 认为字符串和数字相加是有意义的,V8 会将其中的数字转换为字符,然后执行两个字符串的相加操作,最终得到的是一个新的字符串。

在 JavaScript 中,类型系统是依据 ECMAScript 标准来实现的,所以 V8 会严格根据ECMAScript 标准来执行。在执行加法过程中,V8 会先通过 ToPrimitive 函数,将对象转换为原生的字符串或者是数字类型,在转换过程中,ToPrimitive 会先调用对象的 valueOf方法,如果没有 valueOf 方法,则调用 toString 方法,如果 vauleOf 和 toString 两个方法都不返回基本类型值,便会触发一个 TypeError 的错误。

理解JS的面向对象

在不同的编程语言中,设计者利用各种不同的语言特征来抽象描述对象。最成功的流派是用‘类’的方式来描述对象,比如C++,java都是基于类的编程语言。还有一种就是基于原型的编程语言,它们利用原型来描述对象,JavaScript就是其中代表。

基于类的编程提倡使用一个关注分类和类之间关系开发模型。在这类语言中,总是先有类,再从类去实例化一个对象,类与类之间又可能会形成继承,组合等关系。类又往往与语言的类型系统整合,形成一定编译时的能力。

与此对比,基于原型的编程更提倡程序员去关注一系列对象实例的行为,而后才去关心如何将这些对象划分到最近的使用方式相似的原型对象,而不是将它们分成类。基于原型的面向对象系统通过‘复制’的方式创建新对象,JS原型系统‘复制操作’的思路是:并不真的去复制一个原型对象,而是使得新对象持有一个原型的引用。原型系统更多与高动态语言配合,提倡运行时的原型修改,这也是JS作者选择原型系统很重要的一个原因。

每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法。对象__proto__属性的值就是它所对应的原型对象。读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找到为止。

什么是ES6?和ES5的区别?

  • 系统库的引入

Es5:需要先使用require导入React包,成为对象,再去进行真正引用;  

Es6:可以使用import方法来直接实现系统库引用,不需要额外制作一个类库对象

  • 导出及引用单个类

Es5:要导出一个类给别的模块用,一般通过module.exports来实现。引用时,则依然通过require方法来获取; 

Es6:可以使用用export default来实现相同的功能,使用import方法来实现导入

  • 组件内部定义方法

Es5:采用的是 ###:function()的形式,方法大括号末尾需要添加逗号; 

Es6:省略了【:function】这一段,并且结尾不需要加逗号来实现分隔。

  • 定义组件的属性类型和默认属性

Es5:属性类型和默认属性分别通过propTypes成员和getDefaultProps方法来实现(这两个方法应该是固定名称的; 

Es6:统一使用static成员来实现。

  • 块级作用域 关键字let,常量const

ES6 新增了let const命令,用来声明局部变量。 

使用var声明的变量,其作用域为该语句所在的函数内,且存在变量提升现象; 使用let声明的变量,其作用域为该语句所在的代码块内,不存在变量提升,要求必须等let声明语句执行完之后,变量才能使用;let不允许在相同作用域内,重复声明同一个变量。     

使用const声明的是常量,在后面出现的代码中不能再修改该常量的值。 const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据 (数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。 

const a = [];   
a.push('Hello'); // 可执行  a = ['Dave'];    // 报错

上面代码中,常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。如果真的想将对象冻结,应该使用Object.freeze方法。

  • ES6支持解构赋值
  • ES6支持箭头函数 Arrow functions

  • ES6支持字符串模板

var name = "Bob", time = "today";
`Hello{name}, how are you {time}?`

  • ES6增加了Class,有constructor,extends,super,但本质上是语法糖(对语言的功能并没有影响,当时更方便程序员使用)
  • ES6增加了Promises

Promises是处理异步操作的对象,使用了 Promise 对象之后可以用一种链式调用的方式来组织代码,让代码更加直观(类似jQuery的deferred 对象)。

Javascript继承机制的设计思想?

www.ruanyifeng.com/blog/2011/0…

JS中__proto__和prototype的区别和关系?

www.zhihu.com/question/34…

对象有属性_proto_,指向该对象的构造函数的原型对象。可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。

函数除了有属性_proto_,还有属性prototype,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型。

JS实现继承的几种方法

segmentfault.com/a/119000001…

juejin.cn/post/684490…

  • 原型链继承:子类型的原型为父类型的一个实例对象。

  • 构造函数继承:在子类型构造函数中用call()调用父类型构造函数。

  • 原型链+构造函数组合继承:通过调用父类的构造函数继承父类的属性并保留传参的优点,然后通过父类实例作为子类的原型实现函数复用。

  • 原型式继承:利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。

  • 寄生式继承:在原型式继承的基础上,增强对象,返回构造函数。

  • 寄生组合式继承:ES5中存在Object.create()的方法,能够代替上面的object方法。Let B = Object.create(A),以A为原型深拷贝生成了B对象,B继承了A的所有属性和方法,再借用构造函数继承实现参数传递。这是最好的实现方法,也是库的实现方法。

  • ES6中class继承。

JS中一切皆对象吗?

juejin.cn/post/684490…

并非JavaScript中的所有内容都是对象,应该说所有内容都可以充当对象。

三个基本类型string,number和boolean,它们有时被当做包装器类型,并且在原始值和包装类型之间进行转换很简单:
原始类型 to 包装类型: new String("abc")
包装类型 to 原始类型: new String("abc").valueOf()

字符串“abc”之类的原始值与new String(“abc”)之类的包装器实例有根本上的不同。 例如(用typeof和instanceof判断时):
typeof "pet"; //"string"
typeof new String("pet"); //"object"
"pet" instanceof String; // false
new String("pet") instanceof String; // true
"pet" === new String("pet"); // false
当在某些基本类型上调用属性或方法时,JavaScript首先将其转换为临时包装器对象,并访问其上的属性/方法,而不会影响原始属性。

为什么JavaScript里面typeof(null)的值是"object"?

www.zhihu.com/question/21…

这是一个bug,JS类型值是存在32 BIT 单元里,32位有1-3位表示TYPE TAG,其它位表示真实值。而表示object的标记位是全0,低三位正好是0。undefined用整数−2^30(负2的30次方,不在整型的范围内)。

JS中的undefined和null区别?

www.ruanyifeng.com/blog/2014/0…

apply()与call()的区别?

www.cnblogs.com/lengyuehuah…

深拷贝和浅拷贝区别及JS如何实现深拷贝

www.cnblogs.com/echolun/p/7…

如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,如果B没变,那就是深拷贝。

全局函数eval()有什么作用?

eval()只有一个参数,如果传入的参数不是字符串,它直接返回这个参数。如果参数是字符串,它会把字符串当成javascript代码进行编译。如果编译失败则抛出一个语法错误(syntaxError)异常。如果编译成功,则开始执行这段代码,并返回字符串中的最后一个表达式或语句的值,如果最后一个表达式或语句没有值,则最终返回undefined。如果字符串抛出一个异常,这个异常将把该调用传递给eval()。

 JavaScript 中的执行环境和作用域链

github.com/nightn/fron…

执行环境 定义了 JS 代码的运行环境,这个执行环境包括了 JS 代码有权访问的变量、对象、函数和 this。

执行环境包含了一个叫做 变量对象 的东西,这个对象保存了环境中定义的所有变量和函数。

执行环境可以分为三类:

全局执行环境 :这是第一行 JS 代码执行时默认的环境,所有的全局代码都在这个环境中执行。
函数执行环境 : 每个函数都有自己的函数执行环境。每当 JS 引擎发现一个函数调用 (注意是调用,而不是声明),它就会创建一个新的函数执行环境。
Eval : 在 eval 函数中的执行环境。

前面说过,每个执行环境都有一个变量对象(VO),函数执行环境包含的变量对象称之为 活动对象(AO) 。

JS引擎 在分析 JS 代码时分为两个过程,一个是编译阶段,另一个是执行阶段。编译阶段主要是处理代码中的变量和函数声明,执行阶段才开始执行具体语句。当 JS 代码加载完成后,JS 引擎首先进行代码的编译阶段,它会在这个编译阶段创建执行环境,JS 引擎通过 3 个步骤完成

执行环境的创建

创建活动对象或者变量对象:活动对象是 JS 中的一个特殊对象,它包含了当前执行环境所有的变量、函数参数和内部函数声明。活动对象和普通对象不同,它没有 proto 原型属性。
创建作用域链:活动对象创建完毕后,JS 引擎会初始化作用域链,作用域链其实就是一个存放了很多变量对象的列表,包括了从全局执行环境的变量对象到当前函数执行环境的变量对象的所有变量对象。JS 引擎就是通过这个作用域链进行变量查找的。

确定 this 的值:初始化作用域链后,JS 引擎开始确定 this 的值。

JS 引擎在编译阶段结束后,会进入执行阶段。在执行阶段,JS 引擎会再次对代码进行扫描,对变量对象进行更新,并执行代码。

ES6 中的块级作用域和暂时性死区(TDZ)

juejin.cn/post/684490…

ES6把执行环境区分为词法环境和变量环境,针对es6中let,const变量声明以及var变量声明的绑定,可以通过两个不同的过程(词法环境,变量环境)区分开来,在创建变量环境过程中所执行的创建作用域链和创建变量对象(VO)都可以在创建词法环境的过程中完成。

词法环境和变量环境区别是:let和const声明的变量,在执行上下文的「创建阶段」是一种「没有值」的状态,而var声明的变量,则已经是「undefined」了,如果在这个阶段访问var声明的变量,会正常得到undefined,但是访问let和const声明的变量,则会报错ReferenceError: variable is not defined,这个阶段就是暂时性死区。

JavaScript 中的事件循环

zhuanlan.zhihu.com/p/33058983

Event Loop 并不是 ES 这门语言所提供的(也就是说,ES 规范并没有提供事件循环的描述),它是 ES 的宿主环境所提供的。ES 仅提供了一个堆(堆内存)和一个栈(函数调用栈)。而 ES 的宿主环境还提供了一个队列(Callback Queue,回调队列)。简单来讲,这个回调队列存放就是等待执行的回调函数。比如 setTimeout 的定时回调函数、button 点击事件处理函数等等。这些回调函数在特定事件触发时会立刻入队(比如定时结束、按钮被点击),注意:是立刻入队、而不是立刻执行。

那么 Callback Queue 中的回调函数在什么时候才会被执行呢?

被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调函数放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环。

严格来讲,Callback Queue 存放的并不是函数,而是消息,所以 Callback Queue 也可以称为消息队列。每个消息都有一个为了处理这个消息相关联的函数,该函数调用时将对应的消息作为输入参数。

Promise里的代码为什么比setTimeout先执行?

当拿到一段JS代码,浏览器或者node环境会把代码传递给JS引擎,并且要求它去执行。在ES3之前,JS本身没有异步执行代码的能力,这就意味着宿主环境传递给JS引擎一段代码,引擎就把代码顺序执行完成,这种宿主发起的任务称为宏观任务。ES5之后JS引入了promise,这样不需要浏览器的安排,JS引擎本身也可以发起任务了,它发起的任务称为微观任务

宏观任务的队列相当于事件循环,在宏观任务中,JS的promise还会产生异步代码,JS必须保证这些异步代码在一个宏观任务中完成,因此,每个宏观任务中又包含了一个微观任务队列。

有了宏观任务和微观任务机制,我们就可以实现JS引擎级和宿主级的任务了,例如:Promise永远添加在微观任务队列尾部,setTimeout等宿主API,则会添加到宏观任务队列。

JS 中 this 关键字?

github.com/nightn/fron…

this指向的是调用该函数的对象。

  1. 普通的函数调用,函数被谁调用,this就是谁。
  2. 构造函数的话,如果不用new操作符而直接调用,那即this指向window。用new操作符生成对象实例后,this就指向了新生成的对象。
  3. 匿名函数或不处于任何对象中的函数指向window 。
  4. 如果是call,apply等,指定的this是谁,就是谁。

JS中的闭包

www.ruanyifeng.com/blog/2009/0…

闭包就是能够读取其他函数内部变量的函数。

由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包可以用在许多地方。它的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。