继承、作用域和闭包

一、继承

ES5 的继承,实质是先创造子类的实例对象 this ,然后再将父类的方法添加到this上面( Parent.apply(this) )。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到 this 上面(所以必须先调用 super 方法),然后再用子类的构造函数修改 this 。所以在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有 super 方法才能调用父类实例。

1. 使用传统方法

function Parent(name){
  this.name = name
}
function Child(name, age){
  Parent.call(this, name)
  this.age = age
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

var c = new Child()

2. 使用Class

类的数据类型是 函数,类本身指向 构造函数。

class Point {
}
typeof Point // "function"
Point === Point.prototype.constructor // true

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}
class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);
    this.color = color;
  }
}
let cp = new ColorPoint(25, 8, 'green');
cp instanceof ColorPoint // true
cp instanceof Point // true

关于 super 的补充:

  • 内部的 this 指向子类。
  • 只能在子类的 constructor 里调用。
  • 可以当作函数使用,也可以当作对象使用。
  • 作为函数时,代表父类的构造函数。
  • 作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

二、作用域

  • ES5 中只有 全局作用域 和 函数作用域,ES6 中新增 块级作用域。

  • 在 JavaScript 中,一个变量名进入作用域的方式有 4 种:

  1. Language-defined:所有的作用域默认都会给出 thisarguments 两个变量名(global没有arguments);
  2. Formal parameters(形参):函数有形参,形参会添加到函数的作用域中;
  3. Function declarations(函数声明):如 function foo() {};
  4. Variable declarations(变量声明):如 var foo,包括 函数表达式。
  • 函数声明和变量声明总是会被移动到它们所在的作用域的顶部,而变量的解析顺序(优先级),与变量进入作用域的4种方式的顺序一致。

  • this 的指向是执行时确定的。

三、闭包

闭包是作用域的一个应用。

  1. 将函数作为返回值,把原函数内的变量带出去
function create() {
  const a = 100
  return function() {
    console.log(a)
  }
}
const fn = create()
const a = 200
fn() //100
  1. 传入一个函数作为参数,调用原函数内的变量
function fn() {
  console.log(a)
}
function print(fn) {
  const a = 200
  fn() 
}
const a = 100
print(fn) //100

四、补充

1. this 的指向

在函数执行时,this 会默认指向 window 先,然后再根据具体情况,赋其他值给 this。严格模式下 this 为 null。

1)全局的普通函数指向window

function f() {
  console.log(this)
}
f() // window

2)对象的函数指向该对象

const obj = {
  f(){
    console.log(this)
  },
  timeout(){
    setTimeout(function () {
        // this 默认指向 window
        console.log(this)
    }, 1000);
  }
}
obj.f() // obj
obj.timeout() // window

3)箭头函数指向上级作用域的 this,箭头函数本身没有 this

const obj = {
  f(){
    console.log(this)
  },
  timeout(){
    console.log(this) 
    setTimeout(() => {
      console.log(this) 
    }, 1000);
  }
}
obj.f() // obj
obj.timeout() // obj obj

4)构造函数/类指向实例本身

class Person{
  constructor(name,age){
    this.name = name;
    this.age = age;
    console.log(this);
  }
}
let p = new Person('lll',18); // Person {name: "lll", age: 18}
****************************************************************
function Person(name, age) {
  this.name = name;
  this.age = age;
  console.log(this);
}
let p = new Person('lll', 18); // Person {name: "lll", age: 18}

5)bind\call\apply 指向传入的第一个实参

bind 方法的返回值是函数,并且需要稍后调用才会执行,而 apply 和 call 则是立即调用。

// 手写bind\call\apply
Function.prototype.myBind = function(context) {
  const args = Array.prototype.slice.call(arguments,1)
  const self = this
  return function() {
    return self.apply(context,args)
  }
}

Function.prototype.myCall = function (context) { 
  context.fn = this
  const args = [...arguments].slice(1)
  context.fn(args)
  delete context.fn
}

Function.prototype.myApply = function(context) {
  context.fn = this
  const arg = [...arguments].slice(1)[0]
  if (!(arg instanceof Array)) {
    throw new TypeError('the second parms must be Array!')
  }
  context.fn(arg)
  delete context.fn
}

2. 闭包的应用

// 用于暂存数据
function createCache () {
  const data = {}
  return {
    set: function(key, value) {
      data[key]= value
    },
    get: function(key) {
      return data[key]
    }
  }
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

浏览器渲染机制 Previous
原型链 Next