深刻理解javascript原型和闭包系列 对原型和闭包等相关知识的讲解,由浅入深,通俗易懂,每一个字都值得细细研究。javascript
1、一切都是对象html
1. typeof操做符输出6种类型:string boolean number undefined function object
2. 数组、null object都是object类型
3. 对象:若干属性的集合。js中,数组是对象,函数是对象,对象是对象
4. 函数和对象的关系:
1. 函数生成对象:经过new构造函数生成实例对象
2. 函数是一种对象:
1. 全部的函数都有一个prototype(原型)属性,是一个对象,这个对象中默认的存在一个constructor属性指向函数自己。
2. 每一个对象都有一个隐藏的属性:`__proto__`,指向建立这个对象的构造函数的prototype
3. 函数也是对象,固然也有`__proto__`属性,指向Function.prototype(大写Function的原型)
4. 总结:
1. 自定义构造函数(也是对象)的`__proto__`指向Function.prototype;
2. Object.`__proto__`也指向Function.protorype;
3. Function.`__proto__`指向Function.prototype(Function这个对象,固然是被Function函数自身建立的,因此指向自身的prototype)
5. Function.prototype这个对象,是被Object建立的对象,它的隐藏`__proto__`属性,指向Object.prototype,最后,Object.prototype指向Null!前端
2、 继承
1. 访问一个对象的属性时,先在基本属性中查找,若是没有找到,沿着`__proto__`这个属性(指向构造函数的prototype)往上找
2. 实际应用中如何区分一个属性是自有的仍是在原型中的呢?——obj.hasOwnProperty(proName)
3.每一个函数都有call、apply方法,length、arguments等属性,确定是继承的,都是继承自Function.prototype
4. 原型的灵活性:
在Java和C#中,你能够简单的理解class是一个模子,对象就是被这个模子压出来的一批一批月饼。
压个啥样,就得是个啥样,不能随便动,动一动就坏了。
而在javascript中,就没有模子了,月饼被换成了面团,你能够捏成本身想要的样子。
3、执行上下文
1. **一段代码**在正真一行一行执行前,浏览器事先作了准备工做:
1. 对变量的**声明**,但不赋值,赋值是在执行到赋值语句时进行的
2. 直接对this进行**赋值**
3. 函数:
1. 函数声明:function foo(){} //把函数名赋值了!
2. 函数表达式: var foo = function(){} //因为是var的变量,与第一种状况同样,只声明,不赋值
4. 总结:
1. var出来的变量,函数表达式,只声明,不赋值,默认undefined
2. this:赋值
3. 函数声明:赋值
2. 执行**代码段**前的这个准备工做,就叫作执行上下文/上下文环境/执行环境
3. 这个**代码段**其实又分为三种状况:
1. 全局代码
2. eval()
3. 函数【重点↓】
4. 函数代码段中的执行上下文有这些数据:
1. var 出来的变量只声明,没赋值
2. this和函数声明已经赋值
3. 特殊的地方:
1. arguments变量和函数的参数都已经被赋值java
function foo(x){ console.log(arguments); console.log(x); } foo(10); //在进入函数体内部开始执行以前,函数体内部的arguments和参数x已经被赋值了
2. **自由变量的取值做用域:赋值**数组
5. 由此可知,函数每调用一次,都会产生一个新的执行上下文环境,由于不一样的调用可能会有不一样的参数传入
6. 函数在定义的时候(不是调用的时候),就已经肯定了函数体内部**自由变量**的**做用域**浏览器
var a =10; function fn(){ console.log(a); //本函数在建立的时候就决定了这个a要取值的做用域:全局做用域 } function bar(f){ var a = 20; f(); } bar(fn); //10
7. 关于this的一点小问题闭包
var obj = { x:10, fn:function(){ function f(){ console.log(this); console.log(this.x); } f(); } }; obj.fn(); //this指window /* obj.fn保存了指向 function(){ function f(){ console.log(this); console.log(this.x); } f(); } 的指针,加一个(): =>obj.fn()表示开始调用: function f(){ console.log(this); console.log(this.x); } f(); 这个f函数在window环境调用执行,因此this指向window */
8. 上下文环境的执行顺序
1. 执行全局代码时,会产生一个全局上下文环境。
2. 每次调用函数时,会产生函数内部的执行上下文环境。
3. 当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再从新回到全局上下文环境。
4. 处于活动状态的执行上下文环境只有一个。app
var a=10; function bar(x){ var b = 5; fn(x+b); }; function fn(y){ var c = 5; console.log(y+c); }; bar(10); ----------------解析---------------------- /* 1. 产生全局上下文执行环境,该声明的声明,该赋值的赋值 1. a = 10 ; fn = function(); bar = function() 2. 到调用bar(10)函数时,进入bar函数内部,产生函数内部的执行上下文环境,该声明的声明,该赋值的赋值 1. b = 5; x = 10; arguments = [10] 3. 接着调用fn函数,进入fn函数内部,产生函数内部的执行上下文环境,该声明的声明,该赋值的赋值 2. c = 5 ; y = 15; 4. fn函数执行完毕,它对应的上下文执行环境都会被销毁,里面保存的数据都没有了 5. bar函数一样如此,它对应的上下文执行环境都会被销毁,数据清除 */
4、自由变量和做用域
1. 做用域:javascript除了全局做用域以外,只有函数能够产生做用域。因此,在声明变量时,全局代码要在代码前端声明,函数中要在函数体一开始就声明好。除了这两个地方,其余地方都不要出现变量声明。并且建议用“单var”形式。
2. 做用域就是一个地盘,最大的用处就是隔离变量,使不一样做用域下相同的变量名不会产生冲突
3. 每一个函数都会建立本身的做用域,做用域在函数**定义**的时候就已经肯定了,而不是在调用的时候肯定函数
var a = 10,b=20; function fn(x){ var a =100, c = 300; function bar(x){ var a = 1000,d = 4000; console.log(x); console.log(a); console.log(b); console.log(c); console.log(d); } bar(100); bar(200); } fn(10); ----------------------解析:-------------------------- /* 1. 产生全局上下文执行环境,该声明的声明,该赋值的赋值 1. a = 10; b=20; fn = function(); 2. 到调用fn(10)函数时,进入fn函数内部,产生函数内部的执行上下文环境,该声明的声明,该赋值的赋值 1. a = 100 ; c = 300 ; x = 10 ; arguments = [10] ; bar = function() 3. 到调用bar(100)函数时,进入bar函数内部,产生函数内部的执行上下文环境,该声明的声明,该赋值的赋值 1. a = 1000 ; d = 4000 ; x = 100; 4. 到调用bar(200)函数时,进入bar函数内部,产生函数内部的执行上下文环境,该声明的声明,该赋值的赋值 1. a = 1000 ; d = 4000 ; x = 200; 2. 与bar(100)函数相比较,同一个做用域,不一样的调用,产生不一样的执行上下文环境 5. 总结: 1. 做用域只是一个“地盘”,一个抽象的概念,其中没有变量。 2. 要经过做用域对应的执行上下文环境来获取变量的值。 3. 同一个做用域下,不一样的调用会产生不一样的执行上下文环境,继而产生不一样的变量的值。 4. 做用域中变量的值是在执行过程当中产生的肯定的,而做用域倒是在函数建立时就肯定了。 5. 因此,若是要查找一个做用域下某个变量的值,就须要找到这个做用域对应的执行上下文环境,再在其中寻找变量的值。 */
4. 自由变量:在函数内部中使用的变量x,却没有在函数内部声明,也就是在其余做用域中声明的,对这个函数来讲,x就是自由变量this
var x= 10; function fn(){ var b = 20; console.log(x+b); //x就是自由变量 } /* 1. b从本做用域中取,x就要到另外的做用域中取, 2. 是到fn的父级做用域取吗?不是的! */ --------------------分割线--------------------------- var x= 10; function fn(){ console.log(x); } function show(f){ var x = 20; function foo(){ f(); //10 } foo(); } show(fn); //所以:要到建立这个函数的那个做用域中取值——是“建立”,而不是“调用”,切记切记。 //建立fn函数的做用域是window,因此,x = 10; /* 总结一下: 1. 先在当前做用域查找变量x,没有找到则继续; 2. 若是当前做用域是全局做用域,则证实x未定义,结束;不然继续; 3. 不是全局做用域,那就是函数做用域,将**建立该函数的做用域**做为当前做用域; 4. 跳到第一步循环…… */
5. 必须屡次重复强调:要去**建立**这个函数的做用域取值,而不是“父做用域”。
5、最后的闭包:
1. 通常状况下,当一个函数被调用完成以后,其执行上下文环境将被销毁,其中的变量也会被同时销毁。
2. 但有些状况下,函数调用完,其执行上下文环境没有销毁,其中的变量也还在内存中,这就是闭包的核心内容。
function fn(){ var max = 10 ; return function bar(x){ if(x>max){console.log(x);} }; } var f1 = fn(),max = 100 ; f1(15); ----------------解析:--------------------- /* 1. 执行全部代码前,先生成全局上下文执行环境,该声明的声明,该赋值的赋值 1. fn = function() ; f1 = undefined ; max = 100; 2. 执行fn()函数,进入fn函数的上下文执行环境,声明、赋值 1. max = 10 ; bar = function() ; 2. 执行fn函数里的语句,把bar函数return出去,赋值给fn 3. 【重点】此时,fn函数执行完毕了,按理来讲,fn的上下文执行环境应该被销毁,变量从内存清除,但在这里不能这么作!! 1. 由于执行fn函数的过程当中返回了一个bar函数,这个bar函数的特别之处在于, 它建立了一个本身独立的做用域。 2. 在bar函数内部,有一个max自由变量,若是本身没有,就要到建立这个函数的做用域中找,就是fn函数做用域中的max 3. 因此,这个max不能被销毁,fn函数的上下文执行环境不能被销毁,依然存在于执行栈中。 4. 也就是说,执行完var f1 = fn() ,max = 100;以后,全局上下文环境将变为活动状态,可是fn()上下文环境依然会在执行上下文栈中。 5. 建立bar函数是在执行fn函数时建立的。fn()早就执行结束了,可是fn()执行上下文环境还存在与栈中,里面的变量声明、赋值都还在, 6. 所以bar(15)时,max能够查找到。若是fn()上下文环境销毁了,那么max就找不到值了。 */
6、上下文环境和做用域的关系 1. 上下文环境:就是一个对象,里面保存了许多属性,全部变量都在这个对象里面存着。对于函数来讲,上下文环境是在调用函数的时候建立 2. 做用域:是一个地盘,一个抽象的概念,规定了变量起做用的范围。除了全局做用域以外只有函数才能建立做用域,做用域在函数定义时肯定,而不是在函数调用时肯定。 3. 关系: 1. 要经过做用域对应的执行上下文环境来获取变量的值。 2. 同一个做用域下,不一样的调用会产生不一样的执行上下文环境,继而产生不一样的变量的值。 3. 做用域中变量的值是在执行过程当中产生的肯定的,而做用域倒是在函数建立时就肯定了。 4. 若是要查找一个做用域下某个变量的值,就须要找到这个做用域对应的执行上下文环境,再在其中寻找变量的值。