Skip to content

js基础-this & 声明提升 & 继承 #3

Description

@jmy2123

this 机制

绑定规则

  1. 默认绑定
  2. 隐式绑定
  3. 显示绑定
  4. new绑定
  • 默认绑定

独立函数调用

function foo() { 
    console.log( this.a );
}
var a = 2
foo() // 2

本例中,函数调用时应用了this的默认绑定,因此this指向全局对象

在代码里,foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则

注意: 如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此this会绑定到undefined

function foo() {
    "use strict"
    console.log( this.a )
}
var a = 2
foo() // typeError: this is undefined

这里有一个微妙但是非常重要的细节,虽然this的绑定规则完全取决于调用位置,但是只有foo() 运行在非 strict mode 模式下时(在非严格模式下定义),默认绑定才会绑定到全局对象;
跟foo()的调用位置是不是在严格模式下无关:

// 如何理解??????? 上面那段话
function foo() {
    console.log( this.a );
}
var a = 2;
(function() {
    "use strict";
    foo(); // 2
})()
  • 隐式绑定
function foo() {
  console.log( this.a );
}
var obj = {
  a: 2,
  foo: foo
};
obj.foo(); // 2

当函数有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个这个上下文对象。因为调用foo() 时this被绑定到obj,因此 this.a 和 obj.a 是一样的。

  • 显示绑定

call() 和 apply()

function foo() { 
    console.log( this.a );
}
var obj = {a:2};
foo.call( obj ); // 2

显示绑定仍然无法解决我们之前提出的丢失绑定问题

  1. 硬绑定

显示绑定的一个变种, 看如下代码

function foo() { 
    console.log( this.a );
}
var obj = {a: 2}
var bar = function () {
    foo.call(obj)
}
bar() // 2
setTimeout(bar, 100) // 2

// 即使如下使用 硬绑定的 bar 也不可能再修改它的值
bar.bind(window) // 2

分析下这种变种是如何工作的: 手动创建函数 bar,bar函数中给foo显示绑定了obj,之后无论怎样调用bar 总会显示绑定到obj上。这种绑定是一种显示的强制绑定,称为硬绑定

硬绑定的典型应用场景:

场景1.就是创建一个包裹函数,传入所有的参数并返回接收到的所有值,如下:

function foo(something) { 
    console.log( this.a, something ); 
    return this.a + something;
}
var obj = { a:2 };
var bar = function() {
    return foo.apply( obj, arguments );
};
var b = bar( 3 ); // 2 3

场景2.创建一个可以重复使用的辅助函数

function foo(something) {
    console.log(this.a, something)
    return this.a + something
}
function bind(fn, obj) {
    return function () {
        return fn.apply(obj, arguments)
    }
}

var obj = {a: 2}
var bar = bind(foo, obj)

var b = bar(3) // 2 3
console.log(b) // 5

ES5中提供了内置的方法Function.prototype.bind,用法如下:

function foo(something) {
    console.log(this.a, something)
    return this.a + something
}
var obj = {a: 2}
var bar = foo.bind(obj)
var b = bar(3) // 2 3
console.log(b) // 5

bind 会返回一个硬编码的新函数,它会把参数设置为this的上下文并调用原始函数

  1. API调用的上下文

第三方库的许多函数,以及 JavaScript 语言和宿主环境中许多新的内置函数,都提供了一 个可选的参数,通常被称为“上下文”(context),其作用和 bind(..) 一样,确保你的回调 函数使用指定的 this。

例如forEach、map 等

function foo(el) {
console.log( el, this.id );
}
var obj = {
id: "awesome"
};
// 调用 foo(..) 时把 this 绑定到 obj [1, 2, 3].forEach( foo, obj );
// 1 awesome 2 awesome 3 awesome

这些函数实际上就是通过 call(..) 或者 apply(..) 实现了显式绑定,这样你可以少些一些 代码。

  • new 绑定

new 是最后一种可以影响函数调用时this绑定行为的方法,我们称之为new绑定

使用new来调用函数或者说发生构造函数调用时,会自动执行下面的操作

  1. 创建或者说构造一个全新的对象
  2. 这个新对象会被执行[[原型]]连接(暂时忽略)
  3. 这个新对象会绑定到函数调用的this
  4. 如何函数没有返回其他对象,那么new 表达式中的函数调用会自动返回这个新对象。

重点看1 3 4 步,2先不进行详细讲解,思考下面代码:

function foo(a) { 
    this.a = a;
}
var bar = new foo(2); 
console.log( bar.a ); // 2

使用new来调用调用foo(..) 时,我们会构造一个新对象bar,并把他绑定到foo(..)调用中的this上

优先级

找到函数的调用位置并判断应当应用哪条规则,如果某个位置可以应用多条规则该怎么办,为了解决这个问题,就必须给这些规则设定优先级。

首先,默认绑定 优先级最低

比较 隐式绑定 和 显示绑定

=》 显示绑定优先级更高

比较 new绑定 和 隐式绑定

=》 new绑定优先级更高

声明提升

概念:变量和函数的声明会在物理层面移动到代码的最前面,但这么说也并不准确。实际上变量和函数在代码里的位置是不会变的,而是在编译阶段被放入内存中。

常见的示例如下:

var x = 1;                 // 声明 + 初始化 x
console.log(x + " " + y);  // '1 undefined'
var y = 2;                 // 声明 + 初始化 y

//上面的代码和下面的代码是一样的 
var x = 1;                 // 声明 + 初始化 x
var y;                     // 声明 y
console.log(x + " " + y);  // y 是未定义的
y = 2;                     // 初始化  y 

关于let

  • 不存在变量提升
a = 1; 
var a;
console.log(a) // 1

b = 1;
let b;
console.log(b) // 引用报错 Uncaught ReferenceError: b is not defined
  • 作用域

let 的作用域是块级的

{var a = 1}
{let d = 1}

console.log(a)
console.log(d) // Uncaught ReferenceError: d is not defined
  • 不能重复声明
var a = 1;
var a = 2;

let b = 1;
let b = 2;  // Uncaught SyntaxError: Identifier 'b' has already been declared
  • 暂时死区
console.log(a) // undefined
console.log(b) // 报错 Uncaught ReferenceError: b is not defined

var a = 1
var b = 1

给一个例子来看一下输出结果:

var a = 1;

function b() {
    a = 10;
    return;

    function a() {}
}
b();
console.log(a) // 1

// 解析:函数b 等同于以下写法
function b() {
    var a = function () {}
    a = 10
    return
}

继承

其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

首先了解下原型链

了解原型链之前先知道,每个实例对象(object)都有一个私有属性(称之为__proto__)指向它的原型对象(prototype),该原型对象也有一个自己的原型对象(为__proto__) ,层层向上直到一个对象的原型对象为 null

构造函数(Super) 有一个原型对象 prototype,
Super的原型对象prototype 包含一个指向构造函数的指针,
Super的实例(ins_sp)包含一个指向Super原型对象的内部指针

构造函数(Sub) 有一个原型对象 prototype,
Sub的原型对象prototype 包含一个指向构造函数的指针,
Sub的实例(ins_sb) 包含一个指向Sub原型对象的内部指针

⚠️注意:让Sub的原型对象prototype 等于Super 的实例(ins_sb)

此时:Sub的原型对象 包含一个指向Super的原型对象的指针

哎,好绕~~~~

其中 让Sub的原型对象prototype 等于Super 的实例(ins_sp) 就是所谓的继承

代码大致如下:

function SuperType() {
    this.property = true
}
SuperType.prototype.getSuperValue = function () {
    return this.property
}

function SubType() {
    this.subproperty = false
}
// ⚠️注意
SubType.prototype = new SuperType()

SubType.prototype.getSubValue = function () {
    return this.subproperty
}

var instance = new SubType()

console.log(instance.getSuperValue()) // 

在上述代码中,我们没有使用SubType默认提供的原型,而是给它换了一个新原型,这个新原型就是SuperType的实例。

于是,新原型不仅具有作为一个SuperType的实例所拥有的全部属性和方法,而且其内部还有一个指针,指向SuperType的原型,SubType的原型又指向SuperTYpe的原型。

其结果就是:instance指向SubType的原型,SubType的原型又指向SuperType的原型。这就是原型链~

好了,再理一下为何getSuperValue()方法在SuperType.prototype中,但是property则位于SubType.property中?

这是因为property是实例属性,而getSuperValue则是一个原型方法。既然SubType.property现在是SuperType的实例,那property 就当然位于SubType.property中了

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions