Javascript 基础

Posted by Gemicat on January 6, 2020

原型 & 原型链

javascript 中 undefined, number, string, boolean 属于简单的值类型,函数、数组、对象、null、Number 都是对象,属于引用类型。

判断一个变量是不是对象非常简单,值类型的类型判断用typeof,引用类型的类型判断用instanceof。

var fn = function () { };
console.log(fn instanceof Object);  // true

对象都是通过函数创建的: 字面量创建只是一种语法糖

// var obj = { a: 10, b: 20 };
var obj = new Object();
obj.a = 10;
obj.b = 20;
// 其中 Object 是函数
console.log(typeof Object);  // function
console.log(typeof obj);  // object

javascript 中函数具有默认属性 prototype,值是一个对象,该对象有一个 constructor 属性指回函数本身。

每个对象都有一个隐式属性 __proto__ 指向创建该对象的函数的 prototype,Object.prototype 指向 null。

null 表示“没有对象”,即该处不应该有值。

const obj = new Object()
obj.__proto__ === Object.prototype

Instanceof 的判断规则是(A instanceof B):顺着 A 的 __proto__ 属性和 B 的 prototype 属性向上查找,如果两者重合返回 true。

image

访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。

使用 hasOwnProperty() 方法可以判断属性是否是自身的。

作用域

作用域是指程序源代码中定义变量的区域,规定了如何查找变量,JavaScript 采用词法作用域(静态作用域)。

核心就是: 函数的作用域在函数定义的时候就决定了,函数的作用域基于函数创建的位置。

var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar(); // 输出:1

执行上下文

当 JavaScript 执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

变量对象

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。

  • 全局上下文的变量对象初始化是全局对象
  • 函数上下文的变量对象初始化只包括 Arguments 对象
  • 在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
  • 在代码执行阶段,会再次修改变量对象的属性值
console.log(foo); // 输出函数

function foo(){}

var foo = 1;

这是因为在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

作用域链

当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

this

todo

闭包

闭包是指那些能够访问自由变量的函数(既不是函数参数也不是函数局部变量的变量)

ECMAScript中,闭包指的是:

  1. 理论角度:所有的函数,因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
  2. 实践的角度:以下函数才算闭包
    1. 即时创建它的上下文已经销毁,仍然存在的函数(比如内部函数从父函数返回)
    2. 在代码中引用了自由变量

进阶

函数传参

ECMAScript中所有函数的参数,如果是基本类型,是按值传递的。如果是引用类型的值则是共享传递(传递对象引用的副本),可以理解为值的拷贝,所以被认为也是按值传递

call、apply、bind 的模拟实现

Function.prototype.call = function (context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}
Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    } else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}
Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}