ES 基础知识点总结(一)

知识点梳理

  • 变量类型
    • JS的数据类型分类和判断
    • 值类型和引⽤类型
  • 原型与原型链(继承)
    • 原型和原型链定义
    • 继承写法
  • 作⽤域和闭包
    • 执⾏上下⽂
    • this
    • 闭包是什么
  • 异步
    • 同步vs异步
    • 异步和单线程
    • 前端异步的场景
  • ES6/7新标准的考查
    • 箭头函数
    • Module
    • Class
    • Set和Map
    • Promise

变量类型

JavaScript是⼀种弱类型脚本语⾔,所谓弱类型指的是定义变量时,不需要什么类型,在程序运⾏过程中会⾃动判断类型。

ECMAScript中定义了7种原始类型:BooleanStringNumberNullUndefinedSymbol(ES6新定义)

  • Boolean
  • String
  • Number
  • Null
  • Undefined
  • Symbol(ES6新定义)
  • BigInt(新增)

注意:原始类型不包含Object。

题⽬:类型判断⽤到哪些⽅法?

typeof

typeof xxx 得到的值有以下⼏种类型: undefined,boolean,number,string,object,function、symbol,⽐较简单,不再⼀⼀演示了。这⾥需要注意的有三点:

  • typeof null结果是object,实际这是 typeof 的⼀个bug,null是原始值,⾮引⽤类型
  • typeof [1, 2]结果是object,结果中没有array这⼀项,引⽤类型除了function其他的全部都是object
  • typeof Symbol()typeof获取symbol类型的值得到的是symbol,这是ES6新增的知识点

instanceof

⽤于实例和构造函数的对应。例如判断⼀个变量是否是数组,使⽤typeof⽆法判断,但可以使⽤[1,2] instanceof Array来判断。因为,[1,2]是数组,它的构造函数就是Array。同理:

js function Foo(name) { this.name = name } varfoo = newFoo('bar') console.log(fooinstanceofFoo) // true

题⽬:值类型和引⽤类型的区别

除了原始类型,ES还有引⽤类型,上⽂提到的typeof识别出来的类型中,只有objectfunction是引⽤类型,其他都是值类型。

根据JavaScript中的变量类型传递⽅式,⼜分为值类型引⽤类型,值类型变量包括Boolean、String、Number、Undefined、Null,引⽤类型包括了Object类的所有,如Date、Array、Function等。在参数传递⽅式上,值类型是按值传递,引⽤类型是按共享传递。下⾯通过⼀个⼩题⽬,来看下两者的主要区别,以及实际开发中需要注意的地⽅。

下⾯通过⼀个测试,来看下两者的主要区别,以及实际开发中需要注意的地⽅。

js //值类型 var a = 10 var b = a b = 20 console.log(a) // 10 console.log(b) // 20 上述代码中,a,b都是值类型,两者分别修改赋值,相互之间没有任何影响。再看引⽤类型的例⼦: js //引用关系 var a = {x: 10, y: 20} var b = a b.x = 100 b.y = 200 console.log(a) //{ x: 100, y: 200 } console.log(b)//{ x: 100, y: 200 }

说出下⾯代码的执⾏结果,并分析其原因。 js function foo(a){ a = a * 10 } function bar(b) { b.value = 'new' } foo(a); bar(b); console.log(a); // 1 console.log(b); // value: new 通过代码执⾏,会发现: + a的值没有发⽣改变 + ⽽b的值发⽣了改变

这就是因为Number类型的a是按值传递的,⽽Object类型的b是按共享传递的。

JS中这种设计的原因是:按值传递的类型,复制⼀份存⼊栈内存,这类类型⼀般不占⽤太多内存,⽽且按值传递保证了其访问速度。按共享传递的类型,是复制其引⽤,⽽不是整个复制其值(C语⾔中的指针),保证过⼤的对象等不会因为不停复制内容⽽造成内存的浪费。

引⽤类型经常会在代码中按照下⾯的写法使⽤,或者说容易不知不觉中造成错误!

js var obj = { a: 1, b: [1, 2, 3] } var a = obj.a var b = obj.b a = 2 b.push(4) console.log(obj, a, b) 虽然obj本身是个引⽤类型的变量(对象),但是内部的ab⼀个是值类型⼀个是引⽤类型,a的赋值不会改变obj.a,但是b的操作却会反映到obj对象上。

原型和原型链

JavaScript是基于原型的语⾔,原型理解起来⾮常简单,但却特别重要,下⾯还是通过题⽬来理解下JavaScript的原型概念。

题⽬:如何理解JavaScript的原型

  • 所有的引⽤类型(数组、对象、函数),都具有对象特性,即可⾃由扩展属性(null除外)
  • 所有的引⽤类型(数组、对象、函数),都有⼀个__proto__属性,属性值是⼀个普通的对象
  • 所有的函数,都有⼀个prototype属性,属性值也是⼀个普通的对象所有的引⽤类型(数组、对象、函数),__proto__属性值指向它的构造函数的prototype属性值

```js // 要点一:自由扩展属性 var obj = {};obj.a = 100 var arr = [];arr.a = 100 function fn() {} fn.a = 100

// 要点二: proto console.log(obj.proto) console.log(arr.proto) console.log(fn.proto)

// 要点三: 函数要有prototype console.log(fn.prototype)

// 要点四: 引用类型的proto属性值向它的构造函数的 prototype console.log(obj.proto === obj.prototype) ```

原型

先来看一个简单的代码示例 ```js // 构造函数 function Foo(name, age) { this.name = name } Foo.prototype.alertName = function () { alert(this.name) }

// 创建示例 var f = new Foo('zhangsan') f.alertName = function () { console.log(this.name); }

// 测试 f.printName() f.alertName() ```

执⾏printName时很好理解,但是执⾏alertName时发⽣了什么?这⾥再记住⼀个重点当试图得到⼀个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找,因此f.alertName就会找到Foo.prototype.alertName。那么如何判断这个属性是不是对象本身的属性呢?使⽤hasOwnProperty,常⽤的地⽅是遍历⼀个对象的时候。

那么如何判断这个属性是不是对象本身的属性呢?使⽤hasOwnProperty,常⽤的地⽅是遍历⼀个对象的时候。

js var item for (item in f) { // 高级浏览已经在 for in 中屏蔽来自原型的属性, // 但是在这里建议去加上这个判断,保证程序的健壮性 if(f.hasOwnProperty(item)) { console.log(item); } }

题⽬:如何理解JS的原型链

原型链

还是接着上⾯的示例,如果执⾏f.toString()时,⼜发⽣了什么? ```js // 省略n行

// 测试 f.printName() f.alertName() f.toString() `` 因为f本身没有toString(),并且f.proto(即Foo.prototype`)中也没有toString。这个问题还是得拿出刚才那句话——当试图得到⼀个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的_proto_(即它的构造函数的prototype)中寻找。

如果在f.proto中没有找到toString,那么就继续去f.proto.proto中寻找,因为f.proto就是⼀个普通的对象⽽已嘛! + f.__proto__Foo.prototype,没有找到toString,继续往上找 + f.__proto__.__proto__即Foo.prototype.proto。Foo.prototype就是⼀个普通的对象,因此Foo.prototype.proto就是Object.prototype,在这⾥可以找到toString

  • 因此f.toString最终对应到了Object.prototype.toString这样⼀直往上找,你会发现是⼀个链式的结构,所以叫做“原型链”。如果⼀直找到最上层都没有找到,那么就宣告失败,返回undefined。最上层是什么——Object.prototype.__proto__ === null

作用域链中的this

所有从原型或更⾼级原型中得到、执⾏的⽅法,其中的this在执⾏时,就指向了当前这个触发事件执⾏的对象。因此printNamealertName中的this都是f

作⽤域和闭包

题⽬:现在有个HTML⽚段,要求编写代码,点击编号为⼏的链接就alert弹出其编号

```html

  • 编号1,点击我请弹出1
  • 2
  • 3
  • 4
  • 5

⼀般不知道这个题⽬⽤闭包的话,会写出下⾯的代码: js var list = document.getElementsByTagName('li') for (var i = 0; i < list.length; i++ ) { list[i].addEventListener('click', function () { alert(i + 1) }, true) } 实际上执⾏才会发现始终弹出的是6,这时候就应该通过闭包来解决: js var list = document.getElementsByTagName('li') for (var i = 0; i < list.length; i++ ) { list[i].addEventListener('click', function (i) { alert(i + 1) }(i), true) } ```

要理解闭包,就需要我们从「执⾏上下⽂」开始讲起。

执⾏上下⽂

从⼀个关于变量提升的知识点,⾯试中可能会遇⻅下⾯的问题,很多候选⼈都回答错误:、 ```js console.log(a); // undefined var a = 100

fn('zhangsan') // 'zhansan' 20 function fn(name) { age = 20 console.log(name, age); var age }

console.log(b); // 报错 // Uncaught ReferenceError: b is not defined b = 100 ```

在⼀段JS脚本(即⼀个<script>标签中)执⾏之前,要先解析代码(所以说JS是解释执⾏的脚本语⾔),解析的时候会先创建⼀个全局执⾏上下⽂环境,先把代码中即将执⾏的(内部函数的不算,因为你不知道函数何时执⾏)变量、函数声明都拿出来。变量先暂时赋值为undefined,函数则先声明好可使⽤。这⼀步做完了,然后再开始正式执⾏程序。再次强调,这是在代码执⾏之前才开始的⼯作。

我们来看下上⾯的⾯试⼩题⽬,为什么aundefined,⽽b却报错了,实际JS在代码执⾏之前,要「全⽂解析」,发现var a,知道有个a的变量,存⼊了执⾏上下⽂,⽽b没有找到var关键字,这时候没有在执⾏上下⽂提前「占位」,所以代码执⾏的时候,提前报到的a是有记录的,只不过值暂时还没有赋值,即为undefined,⽽b在执⾏上下⽂没有找到,⾃然会报错(没有找到b的引⽤)。

另外,⼀个函数在执⾏之前,也会创建⼀个函数执⾏上下⽂环境,跟全局上下⽂差不多,不过函数执⾏上下⽂中会多出thisarguments和函数的参数。

总结一下:

  • 范围:⼀段<script>、js⽂件或者⼀个函数
  • 全局上下⽂:变量定义,函数声明
  • 函数上下⽂:变量定义,函数声明,thisarguments

this

先搞明⽩⼀个很重要的概念——this的值是在执⾏的时候才能确认,定义的时候不能确认为什么呢——因为this是执⾏上下⽂环境的⼀部分,⽽执⾏上下⽂需要在代码执⾏之前确定,⽽不是定义的时候。看如下例⼦ js var a = { name: 'A', fn: function () { console.log(this.name); } } a.fn() // this === a a.fn.call({name:"B"}) // this === {nanme: B} var fn1 = a.fn fn1() // this === window this执行会有不同,主要集中在一下场景中

  • 作为构造函数执⾏,构造函数中
  • 作为对象属性执⾏,上述代码中a.fn()
  • 作为普通函数执⾏,上述代码中fn1()
  • ⽤于call apply bind,上述代码中a.fn.call({name: 'B'})

下⾯再来看看什么是作⽤域和作⽤域链,作⽤域链和作⽤域。

题⽬:如何理解JS的作⽤域和作⽤域链

作⽤域

js if(true) { var name = 'mint' } console.log(name); 从上⾯的例⼦可以体会到作⽤域的概念,作⽤域就是⼀个独⽴的地盘,让变量不会外泄、暴露出去。上⾯的name就被暴露出去了,因此,JS没有块级作⽤域,只有全局作⽤域和函数作⽤域

js var a = 100 function fn() { var a = 200 console.log('fn', a); console.log('global', a); } 全局作⽤域就是最外层的作⽤域,如果我们写了很多⾏JS代码,变量定义都没有⽤函数包括,那么它们就全部都在全局作⽤域中。这样的坏处就是很容易撞⻋、冲突。 ```js //张三写的代码中 var data= {a: 100}

//李四写的代码中 var data= {x: true} ```

这就是为何jQuery、Zepto等库的源码,所有的代码都会放在(function(){....})()中。因为放在⾥⾯的所有变量,都不会被外泄和暴露,不会污染到外⾯,不会对其他的库或者JS脚本造成影响。这是函数作⽤域的⼀个体现。附:ES6中开始加⼊了块级作⽤域,使⽤let定义变量即可,如下: js if (true) { letname='zhangsan' } console.log(name)//报错,因为let定义的name是在if这个块级作用域

作⽤域链

⾸先认识⼀下什么叫做⾃由变量。如下代码中,console.log(a)要得到a变量,但是在当前的作⽤域中没有定义a(可对⽐⼀下b)。当前作⽤域没有定义的变量,这成为⾃由变量。⾃由变量如何得到——向⽗级作⽤域寻找。 js var a = 100 function fn() { var a = 200 console.log(a); console.log(b); } fn() 如果⽗级也没呢?再⼀层⼀层向上寻找,直到找到全局作⽤域还是没找到,就宣布放弃。这种⼀层⼀层的关系,就是作⽤域链js var a = 100 function foo() { var b = 200 function bar() { var c = 300 console.log(a);// 自由变量,顺作用域链想父作用域找 console.log(b);// 自由变量,顺作用域链想父作用域找 console.log(c);// 本作用域的变量 } bar() } foo()

闭包

再来看一个例子,来理解闭包 ```js function Foo() { var a = 100 return function () { console.log(a); } }

var foo = Foo() var a = 200 foo() ⾃由变量将从作⽤域链中去寻找,但是**依据的是函数定义时的作⽤域链,⽽不是函数执⾏时**,以上这个例⼦就是闭包。闭包主要有两个应⽤场景: + 函数作为返回值,上⾯的例⼦就是 + 函数作为参数传递,看以下例⼦ js function Foo() { var a = 100 return function () { console.log(a); } } function Bar(foo) { var a = 200 console.log(foo()); }

var foo = Foo() Bar(foo) ```

异步

同步vs异步

先看下⾯的demo,根据程序阅读起来表达的意思,应该是先打印100,1秒钟之后打印200,最后打印300。但是实际运⾏根本不是那么回事。 js console.log(100); setTimeout(function () { console.log(200); }, 1000) console.log(300); 再对⽐以下程序。先打印100,再弹出200(等待⽤户确认),最后打印300。这个运⾏效果就符合预期要求。 js console.log(100); alert(200); // 1秒钟之后点击确认 console.log(300); 这俩到底有何区别?——第⼀个示例中间的步骤根本没有阻塞接下来程序的运⾏,⽽第⼆个示例却阻塞了后⾯程序的运⾏。前⾯这种表现就叫做异步(后⾯这个叫做同步),即不会阻塞后⾯程序的运⾏

异步和单线程

JS需要异步的根本原因是JS是单线程运⾏的,即在同⼀时间只能做⼀件事,不能“⼀⼼⼆⽤”。

⼀个Ajax请求由于⽹络⽐较慢,请求需要5秒钟。如果是同步,这5秒钟⻚⾯就卡死在这⾥啥也⼲不了了。异步的话,就好很多了,5秒等待就等待了,其他事情不耽误做,⾄于那5秒钟等待是⽹速太慢,不是因为JS的原因。

讲到单线程,我们再来看个真题:

题⽬:讲解下⾯代码的执⾏过程和结果 js var a = true setTimeout(function () { a = false }, 100) while(a) { console.log('while执行了'); }

这是⼀个很有迷惑性的题⽬,不少候选⼈认为100ms之后,由于a变成了false,所以while就中⽌了,实际不是这样,因为JS是单线程的,所以进⼊while循环之后,没有「时间」(线程)去跑定时器了,所以这个代码跑起来是个死循环!

前端异步的场景

  • 定时setTimeout setInterval
  • ⽹络请求,如Ajax<img>加载

Ajax代码示例 js console.log('start'); $.get('./data.json', function (data) { console.log(data); })

img代码示例(常⽤于打点统计) js console.log('start'); var img =- document.createElement('img') // 或者 img = new Image() img.onload = function () { console.log('loaded'); img.onload = null } img.src = './xxx.jpg' console.log('end');

ES6/7新标准的考查

题⽬:ES6箭头函数中的this和普通函数中的有什么不同

箭头函数

箭头函数是ES6中新的函数定义形式,function name(arg1, arg2) {...}可以使⽤(arg1,arg2) => {...}来定义。示例如下: ```js // JS 普通函数 var arr = [1, 2, 3] arr.map(function(item) { console.log(index); return item + 1 })

// ES6 箭头函数 const arr2 = [1, 2, 3] arr.map((item, index) => { console.log(index); return item + 1 }) 箭头函数存在的意义,第⼀写起来更加简洁,第⼆可以解决ES6之前函数执⾏中this是全局变量的问题,看如下代码 js function fn() { console.log('real', this); // {a: 100}, 该作用域下的 this 的真实值 var arr = [1, 2, 3] // 普通JS
arr.map(function(item) { console.log('js', this); // window 普通函数, 这里打印出来的全局变量,令人费解 return item + 1 })

// 箭头函数
arr.map((item, inedx) => {
    console.log('es6', this); // {a: 100} 箭头函数,这里打印出来的就是父作用域的this
    return item + 1
})

}

fn.call({ a: 100 }) ```

题⽬:ES6模块化如何使⽤?

Module

ES6中模块化语法更加简洁,直接看示例。如果只是输出⼀个唯⼀的对象,使⽤export default即可,代码如下:

```js // 创建 util1.js ⽂件,内容如 export default { a: 100 }

// 创建 index.js 文件,内容如 import obj from './until1.js'

console.log(obj) 如果想要输出许多个对象,就不能⽤`default`了,且`import`时候要加`{...}`,代码如下: js // 创建 util2.js文件,内容如 export function fn1() { alert('fn1') }

export function fn2() { alert('fn2') }

// 创建 index.js 文件,内容如 import { fn1, fn2 } from './util2.js' fn1() fn2() ```

题⽬:ES6 class和普通构造函数的区别

class

class其实⼀直是JS的关键字(保留字),但是⼀直没有正式使⽤,直到ES6。ES6的class就是取代之前构造函数初始化对象的形式,从语法上更加符合⾯向对象的写法。例如:

JS构造函数的写法: ```js function MathHandle(x, y) { this.x = x; this.y = y; }

MathHandle.prototype.add = function () { return this.x + this.y; }

var m = new MathHandle(1, 2) console.log(m.add()) ⽤`ES6 class`的写法: js class MathHandle { constructor(x, y) { this.x = x; this.y = y; }

add() {
    return this.x + this.y;
}

}

const m = new MathHandle(1, 2); console.log(m.add()); `` 注意以下⼏点,全都是关于class语法的: +class是⼀种新的语法形式,是class Name {...}这种形式,和函数的写法完全不⼀样 + 两者对⽐,构造函数函数体的内容要放在class中的constructor函数中,constructor即构造器,初始化实例时默认执⾏ +class中函数的写法是add(){...}`这种形式,并没有function关键字

使⽤class来实现继承就更加简单了,⾄少⽐构造函数实现继承简单很多。

JS构造函数实现继承: ```js // 动物 function Animal() { this.eat = function () { console.log('animal eat') } }

// 狗 function Dog() { this.bark = function () { console.log('dog bark') } } Dog.prototype = new Animal() // 哈士奇 const hashiqi = new Dog() `` ES6 class`实现继承:

```js class Animal { constructor(name) { this.name = name; }

eat() {
    console.log(`${this.name} eat`)
}

}

class Dog extends Animal { constructor(name) { super(name); this.name = name } say() { console.log(${this.name} say) } }

const dog = new Dog('哈士奇') dog.say() dog.eat() `` 注意以下两点: + 使⽤extends即可实现继承,更加符合经典⾯向对象语⾔的写法,如Java + ⼦类的constructor⼀定要执⾏super(),以调⽤⽗类的constructor`

题⽬:ES6中新增的数据类型有哪些?

Set和 Map

SetMap都是ES6中新增的数据结构,是对当前JS数组和对象这两种重要数据结构的扩展。由于是新增的数据结构,总结⼀下两者最关键的地⽅:

  • Set类似于数组,但数组可以允许元素重复,Set不允许元素重复
  • Map类似于对象,但普通对象key必须是字符串或者数字,⽽Map的key可以是任何数据类型

Set

Set类似于数组,但数组可以允许元素重复,Set不允许元素重复Map类似于对象,但普通对象的key必须是字符串或者数字,⽽Map的key可以是任何数据类型

```js // 例1 const set = new Set([1,2,3,3,4]) console.log(set);// Set { 1, 2, 3, 4 }

//例2 const set2 = new Set(); [2, 3, 5, 4, 5, 8, 8].forEach(item => set2.add(item)) for(let item of set2) { console.log(item); } // 2 3 5 4 8 ``` Set实例的属性和⽅法有: + size:获取元素数量。 + add(value):添加元素,返回Set实例本身。 + delete(value):删除元素,返回⼀个布尔值,表示删除是否成功。 + has(value):返回⼀个布尔值,表示该值是否是Set实例的元素。 + clear():清除所有元素,没有返回值。

```js const s = new Set() s.add(1).add(2).add(2); //添加元素

s.size; // 2

s.has(1); //true s.has(2); //true s.has(3); //false

s.delete(2); s.has(2);//false

s.clear(); console.log(s); // Set(0) {} ``` Set实例的遍历,可使⽤如下⽅法: + keys():返回键名的遍历器。 + values():返回键值的遍历器。不过由于Set结构没有键名,只有键值(或者说键名和键值是同⼀个值),所以keys()和values()返回结果⼀致。 + entries():返回键值对的遍历器。 + forEach():使⽤回调函数遍历每个成员。

```js let set = new Set(['aaa','bbb','ccc'])

for (let item of set.keys()) { console.log(item); } // aaa // bbb // ccc

for (let item of set.values()) { console.log(item); } // aaa // bbb // ccc

for (let item of set.entries()) { console.log(item); } // [ 'aaa', 'aaa' ] // [ 'bbb', 'bbb' ] // [ 'ccc', 'ccc' ]

set.forEach((value, key) => console.log(key + ':' + value)) // aaa:aaa // bbb:bbb // ccc:ccc ``` Map

Map的⽤法和普通对象基本⼀致,先看⼀下它能⽤⾮字符串或者数字作为key的特性。

```js const map = new Map(); const obj = {p: 'hello world'};

map.set(obj, 'OK'); //OK console.log(map.get(obj));

console.log(map.has(obj)); //true console.log(map.delete(obj)); // true console.log(map.has(obj)); // false `` 需要使⽤new Map()初始化⼀个实例,下⾯代码中setgethasdelete顾名即可思义(下⽂也会演示)。其中,map.set(obj, 'OK')就是⽤对象作为的key(不光可以是对象,任何数据类型都可以),并且后⾯通过map.get(obj)`正确获取了。

Map实例的属性和⽅法如下: + size:获取成员的数量 + set:设置成员key和value + get:获取成员属性值 + has:判断成员是否存在 + delete:删除成员clear:清空所有

```js const map = new Map() map.set('aaa', 100) map.set('bbb', 200)

for (let key of map.keys()) { console.log(key) } // "aaa" // "bbb"

for (let value of map.values()) { console.log(value) } // 100 // 200

for (let item of map.entries()) { console.log(item[0], item[1]) } // aaa 100 // bbb 200

// 或者 for (let [key, value] of map.entries()) { console.log(key, value) } // aaa 100 // bbb 200 ```

Promise

PromiseCommonJS提出来的这⼀种规范,有多个版本,在ES6当中已经纳⼊规范,原⽣⽀持Promise对象,⾮ES6环境可以⽤类似Bluebird、Q这类库来⽀持。

Promise可以将回调变成链式调⽤写法,流程更加清晰,代码更加优雅。

简单归纳下Promise:三个状态两个过程⼀个⽅法,快速记忆⽅法:3-2-1

三个状态:pendingfulfilledrejected 两个过程: + pending→fulfilled(resolve) + pending→rejected(reject) ⼀个⽅法:then 有其他概念,如catchPromise.all/race

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值