继承、作用域和闭包
一、继承
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 种:
- Language-defined:所有的作用域默认都会给出
this
和arguments
两个变量名(global没有arguments
); - Formal parameters(形参):函数有形参,形参会添加到函数的作用域中;
- Function declarations(函数声明):如
function foo() {}
; - Variable declarations(变量声明):如
var foo
,包括 函数表达式。
函数声明和变量声明总是会被移动到它们所在的作用域的顶部,而变量的解析顺序(优先级),与变量进入作用域的4种方式的顺序一致。
this 的指向是执行时确定的。
三、闭包
闭包是作用域的一个应用。
- 将函数作为返回值,把原函数内的变量带出去
function create() {
const a = 100
return function() {
console.log(a)
}
}
const fn = create()
const a = 200
fn() //100
- 传入一个函数作为参数,调用原函数内的变量
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 协议 ,转载请注明出处!