一、8种数据类型
1 | string、number、boolean、undefined、null、object、symbol、bigint |
- null vs.undefined
null在设置对象为空时使用,undefined在设置非对象为空时用 - number vs. bigint(大整数)
当数字非常大时使用bigint,用法:在数字后加 n,且bigint只支持整数不支持小数 - typeof用来检测数据类型(但有两个bug)
1
2typeof 函数 // 'function'
typeof null // 'object'
二、原型链
- 什么是原型
假设有一个普通对象 x={ },x会拥有一个隐藏属性proto,这个属性会指向Object.prototype,即 x.proto = Object.prototype,此时可以说 x的原型 是 Object.prototype,而 proto 属性的作用就是指向x的原型 - 举例说明原型链
比方有一个数组对象 a=[ ],a有一个隐藏属性proto指向Array.prototype,Array.prototype也有一个隐藏属性指向Object.prototype,于是a就有了两层原型,中间通过proto形成一个链条,即
a ===> Array.prototype ===> Object.prototype 如何改变原型(链)
1
2
3const x = Object.create
// 或
const x = new 构造函数() // 会导致 x.__proto = 构造函数.prototype解决了什么问题
在没有class的情况下实现了继承,以下面代码为例1
a ===> Array.prototype ===> Object.prototype
- a是Array的实例,a拥有Array.prototype里的属性(实例化)
- Array继承了Object
- a是Object的间接实例,a拥有Object里的属性(继承)
- 缺点
不支持私有属性,若要支持私有属性需使用class
三、this的指向
详见我的另一篇博客this指向及绑定方法
四、new的过程
- 创建临时对象/空对象/新对象,作为要返回的对象实例
- 绑定原型(绑定共有属性),将临时对象的原型指向构造函数的prototype属性
- 指定this = 临时对象
- 执行构造函数(绑定私有属性),将私有属性绑在临时对象上
- 返回这个临时对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 直观上理解
let Foo = function(name) {
let this = Object.create(Foo.prototype)
this.name = name
return this
}
Foo()
// 代码底层上理解
let objectFactory() {
let obj = {}, // 从 Object.prototype 上克隆一个空的对象
Constructor = [].shift.call(arguments) // 取得外部传入的构造器
obj.__proto__ = Constructor.prototype // 指向正确的原型
let ret = Constructor.apply(obj, arguments) // 借用外部传入的构造器给 obj 设置属性
return typeof ret === 'object' ? ret : obj // 确保构造器总是会返回一个对象
}
let a = objectFactory(Person, 'seven')
console.log(Object.getPrototypeOf(a) === Person.prototype) // true
五、立即执行函数
声明一个匿名函数,然后立即执行它,写法如下:
1
2
3
4
5
6
7
8
9(function(){alert('我是匿名函数')}()) // 括号包住整个表达式
(function(){alert('我是匿名函数')}()) () // 括号包住函数
!function(){alert('我是匿名函数')}()
+function(){alert('我是匿名函数')}()
-function(){alert('我是匿名函数')}()
~function(){alert('我是匿名函数')}()
void function(){alert('我是匿名函数')}()
new function(){alert('我是匿名函数')}()
var x = function(){return '我是匿名函数'}在ES6之前,只能通过立即执行函数来创建局部作用域,且兼容性好
1
2
3
4
5
6
7
8
9
10
11// 创建局部变量 ES5
!function(){
var a
}()
// ES6的 block+let 语法
{
var a
console.log(a) // 可读取到a
}
console.log(a) // 不能读取到a
六、闭包
- 闭包是JS的一种语法特性,JS里的函数都支持闭包
- 闭包 = 函数 + 自由变量(非全局、局部变量的变量)
下面的代码放在了一个非全局环境中(通过立即执行函数可以制造一个非全局环境),这样就是一个闭包的应用
1
2
3
4
5
6
7
8const add = function(){
var count
return () => { // 访问了外部变量的函数
count += 1
}
}()
add() // 相当于 count += 1闭包的作用在于:
- 避免污染全局环境
- 提供对局部变量的间接访问
- 维持变量,不被垃圾回收
- 闭包使用不当可能造成内存泄漏
1
2
3
4
5
6
7
8
9
10function test(){
var x = {name: 'x'};
var y = {name: 'y', content: "-- -- -- --这里有很长很长很长的代码-- -- --"}
return function fn(){
return x
}
}
const myFn = test() // myFn 就是 fn 了
const myX = myFn() // myX 就是 x 了
对于一个正常的浏览器来说,y会在一段时间后自动消失(被垃圾回收掉)
但旧版本的IE浏览器不会回收,这是IE的问题
七、JS如何实现类
方法一:基于原型的class
1 | function Dog(name){ |
方法二:基于class关键字
1 | class Dog { |
八、如何实现继承(子类如何继承父类)
使用原型链
1 | // 声明父类Animal |
1 | // 可替换 关键代码2 |
使用class
1 | class Animal{ |