JS

一、8种数据类型

1
string、number、boolean、undefinednull、object、symbol、bigint
  1. null vs.undefined
    null在设置对象为空时使用,undefined在设置非对象为空时用
  2. number vs. bigint(大整数)
    当数字非常大时使用bigint,用法:在数字后加 n,且bigint只支持整数不支持小数
  3. typeof用来检测数据类型(但有两个bug)
    1
    2
    typeof 函数  // '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
    3
    const x = Object.create
    // 或
    const x = new 构造函数() // 会导致 x.__proto = 构造函数.prototype
  • 解决了什么问题
    在没有class的情况下实现了继承,以下面代码为例

    1
    a ===> Array.prototype ===> Object.prototype
  1. a是Array的实例,a拥有Array.prototype里的属性(实例化)
  2. Array继承了Object
  3. a是Object的间接实例,a拥有Object里的属性(继承)
  • 缺点
    不支持私有属性,若要支持私有属性需使用class

三、this的指向

详见我的另一篇博客this指向及绑定方法

四、new的过程

  1. 创建临时对象/空对象/新对象,作为要返回的对象实例
  2. 绑定原型(绑定共有属性),将临时对象的原型指向构造函数的prototype属性
  3. 指定this = 临时对象
  4. 执行构造函数(绑定私有属性),将私有属性绑在临时对象上
  5. 返回这个临时对象
    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
    8
    const add = function(){
    var count
    return () => { // 访问了外部变量的函数
    count += 1
    }
    }()

    add() // 相当于 count += 1
  • 闭包的作用在于:

  1. 避免污染全局环境
  2. 提供对局部变量的间接访问
  3. 维持变量,不被垃圾回收
  • 闭包使用不当可能造成内存泄漏
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Dog(name){
this.name = name
this.legsNumber = 4
}

Dog.prototype.kind = '狗子'
Dog.prototype.say = function(){
console.log(`汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}
Dog.prototype.run = function(){
console.log(`${this.legsNumber}条腿跑起来。`)
}

const d1 = new Dog('哮天犬') // Dog函数 就是一个类
d1.say()

方法二:基于class关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Dog {
kind = '狗子' // 等价于在 constructor 里写 this.kind = '狗子'
// class未提供在原型上添加非函数属性的方法
constructor(name){
this.name = name
this.legsNumber = 4
}
say(){
console.log(`汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}
run(){
console.log(`${this.legsNumber}条腿跑起来。`)
}
}

const d1 = new Dog('哮天犬')
d1.say()

八、如何实现继承(子类如何继承父类)

使用原型链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 声明父类Animal
function Animal(legsNumber){
this.legsNumber = legsNumber
}
Animal.prototype.kind = '动物'

// 声明子类Dog
function Dog(name){
this.name = name
Animal.call(this, 4) // 关键代码1:继承父类的私有属性
}
Dog.prototype.__proto__ = Animal.prototype // 关键代码2:继承父类的原型,但如果这句被禁用了怎么办

Dog.prototype.kind = '狗'
Dog.prototype.say = function(){
console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}

const d1 = new Dog('哮天犬')
console.dir(d1)
1
2
3
4
// 可替换 关键代码2
var f = function(){}
f.prototype = Animal.prototype
Dog.prototype = new f()

使用class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Animal{
constructor(legsNumber){
this.legsNumber = legsNumber
}
run(){}
}

class Dog extends Animal{
constructor(name){
super(4)
this.name = name
}
say(){
console.log(`汪汪汪~ 我是${this.name},我有${this.legsNumber}条腿。`)
}
}
0%