本文为深入理解javascript原型和闭包系列的摘要笔记
1.一切都是对象
function show(x) {
// 值类型,不是对象
console.log(typeof x); // undefined
console.log(typeof 10); // number
console.log(typeof 'abc'); // string
console.log(typeof true); // boolean
// 引用类型,是对象:函数、数组、对象、null、new Number(10)都是对象
console.log(typeof function () {}); //function
console.log(typeof [1, 'a', true]); //object
console.log(typeof { a: 10, b: 20 }); //object
console.log(typeof null); //object
console.log(typeof new Number(10)); //object
}
show();
对象:若干属性的集合。
- java或者C#中的对象都是new一个class出来的,而且里面有字段、属性、方法,规定的非常严格。
- javascript中,数组是对象,函数是对象,对象还是对象。对象里面的一切都是属性,只有属性,没有方法。
- javascript中,方法也是一种属性。因为它的属性表示为键值对的形式。
var fn = function () {
alert(100);
};
fn.a = 10;
fn.b = function () {
alert(123);
};
fn.c = {
name: "王福朋",
year: 1988
};
2.函数和对象的关系
对象都是通过函数创建的
函数是对象的一种
var fn = function () { };
console.log(fn instanceof Object); // true
对象可以通过函数来创建
function Fn() {
this.name = '王福朋';
this.year = 1988;
}
var fn1 = new Fn();
对象都是通过函数创建的
//var obj = { a: 10, b: 20 };
//var arr = [5, 'x', true];
var obj = new Object();
obj.a = 10;
obj.b = 20;
var arr = new Array();
arr[0] = 5;
arr[1] = 'x';
arr[2] = true;
3.prototype原型
- 每个函数都有一个属性叫做prototype;
- 这个prototype的属性值是一个对象(属性的集合)
- 默认的只有一个叫做constructor的属性,指向这个函数本身
如下图,左侧是一个函数,右侧的方框就是它的原型:
可以在自己自定义的方法的prototype中新增自己的属性
function Fn() { }
Fn.prototype.name = '王福朋';
Fn.prototype.getYear = function () {
return 1988;
};
var fn = new Fn();
console.log(fn.name);
console.log(fn.getYear());
- 即,Fn是一个函数,fn对象是从Fn函数new出来的,这样fn对象就可以调用
Fn.prototype
中的属性。 - 每个对象都有一个隐藏的属性:
__proto__
,这个属性引用了创建这个对象的函数的prototype
。 fn.__proto__ === Fn.prototype
-
__proto__
成为“隐式原型”
4.隐式原型
每个对象都有一个proto属性,指向创建该对象的函数的prototype
var obj = {}
- obj对象的隐式原型:obj这个对象本质上是被Object函数创建的,因此
obj.__proto__=== Object.prototype
,如下图:
即,每个对象都有一个__proto__属性,指向创建该对象的函数的prototype
- Object prototype的隐式原型:
Object prototype
也是一个对象,它的__proto__
指向哪里?null,如下图:
- 函数的隐式原型:函数是通过
new Functoin()
创建的:
5.instanceof
A instanceof B
Instanceof
的判断规则是:沿着A的proto这条线来找,同时沿着B的prototype这条线来找(B线到此为止),如果两条线能找到同一个引用,即同一个对象,那么就返回true。如果找到终点还未重合,则返回false。
function Foo() {}
var f1 = new Foo();
console.log(f1 instanceof Foo);// true
console.log(f1 instanceof Object);// true
![Ojbect Layout](http://www.ibm.com/developerworks/cn/web/1306_jiangjj_jsinstanceof/figure1.jpg)
instanceof
表示的就是一种继承关系,或者原型链的结构
6.继承
-
定义
- javascript中的继承是通过原型链来体现的
- 访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着proto这条链向上找,这就是原型链
例:
function Foo() {}
var f1 = new Foo();
f1.a = 10;
Foo.prototype.a = 100;
Foo.prototype.b = 200;
console.log(f1.a);// 10
console.log(f1.a);// 200
上图中,访问f1.b时,f1的基本属性中没有b,于是沿着proto找到了Foo.prototype.b。
-
对象的继承
区分一个属性到底是基本的还是从原型中找到的:hasOwnProperty
例:
f1.hasOwnProperty(item)
hasOwnProperty
方法来自Object.prototype
,如下图:
-
函数的继承
- 每个函数都有
call
,apply
方法,都有length
,arguments
,caller
等属性 - 函数由
Function
函数创建,因此继承的Function.prototype
中的方法。
函数的原型链 -
Function.prototype
继承自Object.prototype
的方法,因此有hasOwnProperty
方法
7.原型的灵活性
- 对象属性可以随时改动
- 如果继承的方法不合适,可以做出修改
function Foo() {}
var f1 = new Foo();
Foo.prototype.toString = function(){
return 'jyoketsu';
}
console.log(f1.toString());// jyoketsu
- 如果感觉当前缺少你要用的方法,可以自己去创建
例如在json2.js源码中,为Date、String、Number、Boolean方法添加一个toJSON的属性:
创建新方法
8.简述【执行上下文】上
- javascript在执行一个
代码段
之前,都会进行准备工作
来生成执行上下文
; -
代码段
分为三种情况:全局代码,函数体,eval代码; -
准备工作
中完成的数据准备称之为执行上下文
或者执行上下文环境
- 变量、函数表达式——变量声明,默认赋值为undefined
- this——赋值
- 函数声明——赋值
例:
变量:
this:
函数声明、函数表达式:
9.简述【执行上下文】下
- 执行上下文环境:在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空
- 函数每被调用一次,都会产生一个新的执行上下文环境
- 函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域
var a = 10;
function fn(){
console.log(a);// a是自由变量
// 函数创建时,就确定了a要取值的作用域
}
function bar(f){
var a = 20;
f(); //打印"10"而不是"20"
}
bar(fn);
function fn(x){
console.log(arguments);// [10]
console.log(x); // 10
}
fn(10);
上下文环境的数据内容:
内容类型 | 准备动作 |
---|---|
全局代码的上下文环境数据内容为: | |
普通变量(包括函数表达式)如: var a = 10
|
声明(默认赋值为undefined ) |
函数声明,如: function fn() { }
|
赋值 |
this | 赋值 |
如果代码段是函数体,那么在此基础上需要附加: | |
参数 | 赋值 |
arguments | 赋值 |
自由变量的取值作用域 | 赋值 |
10.this
-
this
指向调用该函数的对象 - 被谁直接调用,这个
this
就指向谁。没有的话就是window
- 在函数中this到底取何值,是在函数真正被调用执行的时候确定的,函数定义的时候确定不了。(因为this的取值是执行上下文环境的一部分,每次调用函数,都会产生一个新的执行上下文环境)
在函数中this取何值
- 情况1:函数作为构造函数
function Foo(){
this.name = 'jyoketsu';
this.year = 1991;
console.log(this);// Foo {name:"jyoketsu",year:1991}
}
var f1 = new Foo();
console.log(f1.name);// jyoketsu
console.log(f1.year);// 1991
以上代码中,如果函数作为构造函数用,那么其中的this
就代表它即将new
出来的对象。
如果直接调用Foo函数,this
是window
function Foo(){
this.name = 'jyoketsu';
this.year = 1991;
console.log(this);// window {...}
}
Foo();
- 情况2:函数作为对象的一个属性
如果函数作为对象的一个属性时,并且作为对象的一个属性被调用时,函数中的this指向该对象。
var obj = {
x:10,
fn:function(){
console.log(this); // Object {x:10,fn:function}
console.log(this.x); // 10
}
};
obj.fn();
var obj = {
x:10,
fn:function(){
console.log(this); // Window {...}
console.log(this.x); // undefined
}
};
var fn1 = obj.fn;
fn1();
- 情况3:函数用call或者apply调用
当一个函数被call和apply调用时,this的值就取传入的对象的值。
var obj = {
x:10
}
var fn = function(){
console.log(this); // Object {x:10}
console.log(this.x); // 10
}
fn.call(obj);
- 情况4:全局 & 调用普通函数
在全局环境下,this永远是window
console.log(this === window); // true
普通函数在调用时,其中的this也都是window:
var x = 10;
var fn = function (){
console.log(this); // Window {...}
console.log(this.x); // 10
}
fn();
注意:
var obj = {
x:10,
fn:function(){
function f(){
console.log(this); // Window {...}
console.log(this.x); // undefined
}
f();
}
}
obj.fn();
函数f虽然是在obj.fn内部定义的,但是它仍然是一个普通的函数,this仍然指向window
11.执行上下文栈
执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境。当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个。
其实这是一个压栈出栈的过程——执行上下文栈。如下图:
例:
12.作用域
- javascript没有块级作用域”。所谓“块”,就是大括号“{}”中间的语句。例如if语句
- javascript除了全局作用域之外,只有函数可以创建的作用域
- 作用域在函数定义时就已经确定了。而不是在函数调用时确定。
作用域是一个很抽象的概念,类似于一个“地盘”:
jQuery源码的最外层是一个自动执行的匿名函数:
13.作用域和上下文环境
- 作用域只是一个“地盘”,一个抽象的概念,其中没有变量。
- 要通过作用域对应的执行上下文环境来获取变量的值。
- 作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。
- 如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。
14.从【自由变量】到【作用域链】
- 自由变量:在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。
- 自由变量的取值:
- 1.先在当前作用域查找a,如果有则获取并结束。如果没有则继续;
- 2.如果当前作用域是全局作用域,则证明a未定义,结束;否则继续;
- 3.(不是全局作用域,那就是函数作用域)将创建该函数的作用域作为当前作用域;
- 4.跳转到第一步。
这个一步一步“跨”的路线,我们称之为——
作用域链
。
15.闭包
闭包应用的两种情况:
- 函数作为返回值
- 函数作为参数传递
- 函数作为返回值
function fn(){
var max = 10;
return function bar(x){
if(x > max){
console.log(x);
}
}
}
var f1 = fn();
f1(15);
如上代码,bar函数作为返回值,赋值给f1变量。执行f1(15)时,用到了fn作用域下的max变量的值。
- 函数作为参数被传递
var max = 10;
var fn = function(x){
if(x > max){
console.log(x);
}
};
(function(f){
var max = 100;
f(15);
})(fn);
如上代码中,fn函数作为一个参数被传递进入另一个函数,赋值给f参数。执行f(15)时,max变量的取值是10,而不是100。
- 当一个函数被调用完成之后,其执行上下文环境将被销毁,其中的变量也会被同时销毁。
- 闭包的情况下,函数调用完成之后,其执行上下文环境不会接着被销毁。
fn()调用完成。按理说应该销毁掉fn()的执行上下文环境,但是这里不能这么做。因为执行fn()时,返回的是一个函数。函数的特别之处在于可以创建一个独立的作用域。而正巧合的是,返回的这个函数体中,还有一个自由变量max要引用fn作用域下的fn()上下文环境中的max。因此,这个max不能被销毁,销毁了之后bar函数中的max就找不到值了。