原型 & 原型链
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。
访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__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中,闭包指的是:
- 理论角度:所有的函数,因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
- 实践的角度:以下函数才算闭包
- 即时创建它的上下文已经销毁,仍然存在的函数(比如内部函数从父函数返回)
- 在代码中引用了自由变量
进阶
函数传参
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;
}