JavaScript闭包、this对象的继承、宏微观任务

目录

1.JavaScript闭包

1)闭包概念

 2)闭包的注意点

2. this

1) 关键点

2) 四类调用方式

3.JS宏任务和微任务

1)什么是微任务和宏任务

 2)JS为什么要区分微任务和宏任务

3)微任务和宏任务有哪些

4)微任务和宏任务是怎么执行

4.对象的继承

1)prototype 属性的作用

2)原型链

3)constructor 属性

4)构造函数的继承

5)多重继承


1.JavaScript闭包

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。—引自MDN

在JS中,通俗来讲,闭包就是能够读取外层函数内部变量的函数。  

变量的作用域为两种:全局作用域和局部作用域

1)函数内部可以读取全局变量

let code = 200;
​
function f1() {
    console.log(code);
}
​
f1(); // 200

2)函数外部无法读取函数内部的局部变量

function f1() {
    let code = 200;

    function f2() {
        console.log(code);
    }
} 

1)闭包概念

闭包访问的变量,是每次运行上层函数时重新创建的,是相互独立的。

function f1() {
    let obj = {};

    function f2() {
        return obj;
    }

    return f2;
}

let result1 = f1();
let result2 = f1();
console.log(result1() === result2()); // false

 不同的闭包,可以共享上层函数中的局部变量

function f() {
    let num = 0;

    function f1() {
        console.log(++num);
    }

    function f2() {
        console.log(++num);
    }

    return {f1,
            f2};
}

let result = f();
result.f1(); // 1
result.f2(); // 2


旅行者走路的问题
function factory() {
    var start = 0
    function walk(step) {
        var new_total = start + step
        start = new_total
        return start
    }
    return walk
}
var res = factory()
res(1)
res(2)
res(3)

 2)闭包的注意点

1.由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2.闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值

2. this

1) 关键点

  1. this始终指向调用该函数的对象;

  2. 若没有指明调用的对象,则顺着作用域链向上查找,最顶层为global(window)对象;

  3. 箭头函数中的this是定义函数时绑定的,与执行上下文有关

  4. 简单对象(非函数、非类)没有执行上下文;

  5. 类中的this,始终指向该实例对象;

  6. 箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。

2) 四类调用方式

1.作为对象方法的调用

function f() {
    console.log( this.code );
}
let obj = {
    code: 200,
    f: f
};
obj.f(); // 200

2.纯粹的函数调用

function f() {
    console.log( this.code );
}
// 此处,通过var(函数作用域)声明的变量code会绑定到window上;如果使用let(块作用域)声明变量code,则不会绑定到window上,因此下面的2次函数调用f(),会输出undefined
// let code = 200;
var code = 200;
f(); // 200
code = 404;
f(); // 404

3.作为[构造函数]调用

code = 404
function A() {
    this.code = 200
    this.callA = function() {
        console.log(this.code)
    }
}

var a = new A()
a.callA() // 200, callA在new A返回的对象里

4.使用apply、call、bind调用

(1)apply

var code = 404;
let obj = {
    code: 200,
    f: function() {
        console.log(this.code);
    }
}

obj.f(); // 200, 实际上是作为对象的方法调用
obj.f.apply(); // 404,参数为空时,默认使用全局对象global,在此处为对象window
obj.f.apply(obj); //200,this指向参数中设置的对象

(2)call

 function f() {
    console.log( this.code );
}
var obj = {
    code: 200
};
f.call( obj ); // 200

(3)bind

// bind返回一个新的函数
function f(b) {
    console.log(this.a, b);
    return this.a + b;
}
var obj = {
    a: 2
};
var newF = f.bind(obj);
var result = newF(3); // 2 3
console.log(result); // 5

3.JS宏任务和微任务

1)什么是微任务和宏任务

首先,我们要先了解下 Js 。js 是一种单线程语言,简单的说就是:只有一条通道,那么在任务多的情况下,就会出现拥挤的情况,这种情况下就产生了 ‘多线程’ ,但是这种“多线程”是通过单线程模仿的,也就是假的。那么就产生了同步任务和异步任务。

 2)JS为什么要区分微任务和宏任务

(1)js是单线程的,但是分同步异步

(2)微任务和宏任务皆为异步任务,它们都属于一个队列

(3)宏任务一般是:script、setTimeout、setInterval、postMessage

(4)微任务:Promise.then ES

(5)先执行同步再执行异步,异步遇到微任务,先执行微任务,执行完后如果没有微任务,就执行下一个宏任务,如果有微任务,就按顺序一个一个执行微任务

3)微任务和宏任务有哪些

(1)宏任务一般是:script、setTimeout、setInterval、postMessage

(2)微任务:Promise.then  

4)微任务和宏任务是怎么执行

4执行顺序:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。

这里容易产生一个错误的认识:就是微任务先于宏任务执行。实际上是先执行同步任务然后在执行异步任务,异步任务是分宏任务和微任务两种的。

4.对象的继承

面向对象编程很重要的一个方面,就是对象的继承。A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。

大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript 语言的继承不通过 class,而是通过“原型对象”(prototype)实现,本章介绍 JavaScript 的原型链继承。

ES6 引入了 class 语法,基于 class 的继承不在这个教程介绍,请参阅《ES6 标准入门》一书的相关章节

1)prototype 属性的作用

JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。

下面,先看怎么为对象指定原型。JavaScript 规定,每个函数都有一个prototype属性,指向一个对象。

function f() {}
typeof f.prototype // "object"

上面代码中,函数f默认具有prototype属性,指向一个对象。

对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。

构造函数的一个方法,或者一个属性
function Animal(name) {
  this.name = name;
}
Animal.prototype.color = 'white';

var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');

cat1.color // 'white'
cat2.color // 'white'

 上面代码中,构造函数Animalprototype属性,就是实例对象cat1cat2的原型对象。原型对象上添加一个color属性,结果,实例对象都共享了该属性。

2)原型链

JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain):对象到原型,再到原型的原型……

a---->b------>c------>d-------->Object

如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOftoString方法的原因,因为这是从Object.prototype继承的。

那么,Object.prototype对象有没有它的原型呢?回答是Object.prototype的原型是nullnull没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null

Object.getPrototypeOf(Object.prototype)
// null

3)constructor 属性

prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。

function P() {}
P.prototype.constructor === P // true

 由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承。

function P() {}
var p = new P();

//应该有这个属性吗
p.constructor === P // true

p.constructor === P.prototype.constructor // true

p.hasOwnProperty('constructor') // false

上面代码中,p是构造函数P的实例对象,但是p自身没有constructor属性,该属性其实是读取原型链上面的P.prototype.constructor属性。

4)构造函数的继承

 让一个构造函数继承另一个构造函数,是非常常见的需求。这可以分成两步实现。第一步是在子类的构造函数中,调用父类的构造函数。

function Sub(value) {
  Super.call(this);
  this.prop = value;
}

上面代码中,Sub是子类的构造函数,this是子类的实例。在实例上调用父类的构造函数Super,就会让子类实例具有父类实例的属性。

第二步,是让子类的原型指向父类的原型,这样子类就可以继承父类原型。

Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.prototype.method = '...';

上面代码中,Sub.prototype是子类的原型,要将它赋值为Object.create(Super.prototype),而不是直接等于Super.prototype。否则后面两行对Sub.prototype的操作,会连父类的原型Super.prototype一起修改掉。

5)多重继承

JavaScript 不提供多重继承功能,即不允许一个对象同时继承多个对象。但是,可以通过变通方法,实现这个功能。

function M1() {
  this.hello = 'hello';
}

function M2() {
  this.world = 'world';
}

function S() {
  M1.call(this);
  M2.call(this);
}

// 继承 M1
S.prototype = Object.create(M1.prototype);
// 继承链上加入 M2
Object.assign(S.prototype, M2.prototype);

// 指定构造函数
S.prototype.constructor = S;

var s = new S();
s.hello // 'hello'
s.world // 'world'

上面代码中,子类S同时继承了父类M1M2。这种模式又称为 Mixin(混入)。

你可能感兴趣的:(javascript,开发语言,ecmascript)