1 | function Fn() { } |
fn.__proto__ === Fn.prototype
也就是说实例化对象的隐式原型全等于创建这个对象的函数的显示原型__proto__
属性,指向创建该对象的函数的prototype。但是Object.prototype
是一个特例,它的__proto__
指向的是null!!!
Function.__proto__
指向Function.prototype
形成一个循环
__proto__
这条线来找,同时沿着B的prototype
这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true,如果找到终点还未重合,则返回false。1 | function Foo() { } |
__proto__
这条链向上找,这就是原型链。1 | for(var item in f1){ |
hasOwnProperty
特别是在for...in...
循环中hasOwnProperty
方法是从哪里来的呢?f1本身没有,Foo.prototype
中也没有。不难发现其实它是从Object.prototype
中来的。由于所有的对象的原型链都会找到Object.prototype
,因此所有的对象都会有Object.prototype
的方法。这就是所谓的继承
再例如所有函数都有call
、apply
方法,都有length
、arguments
、caller
等属性。为什么会有呢,就是因为继承
。函数是由Function
函数创建的,因此继承的Function.prototype
中的方法。
1 | var obj = {a:10 , b:20} |
继承的方法不合适,可以做出修改。如上Object和array的toString()方法不一样,肯定是Array.prototype.toString()
方法做了修改。
内置方法的原型属性可以手动添加和自定义,所以在添加之前最好先做一次判断,判断这个属性是否本来就存在。
1 | console.log(a); // a is not defined |
1 | console.log(this); // Window{top: Window, window:...} |
this
都是有值得。但是this
和变量不同,在预解析的时候变量之声明不赋值,但是this
会直接赋值。1 | console.log(f1); // function f1(){} |
对于函数,对待函数表达式就像变量一样,对待函数声明时,却把整个函数赋值了。
函数每被调用一次,都会产生一个新的执行上下文环境,因为不同的调用可能会有不同的参数。
且函数在定义的时候(不是在调用的时候),就已经确定了函数体内部自由变量的作用域。
所谓构造函数就是用来new
一个对象的函数。严格来讲所有的函数都可以new
一个对象,但是有些函数的定义就是为了去new
一个对象,而有些函数则不是。PS: 构造函数的函数名第一个字母大写(规则约定)。如:Object、Array、Function等等。
1 | function Foo(){ |
那么在构造函数的prototype
中,this
代表什么
1 | function Fn(){ |
可以在上面的代码中看出来this
指向了f1
这个对象。其实,不仅仅是构造函数prototype,即便是在整个原型链中,this代表的也都是当前对象的值。
1 | function Foo(){ |
由上面的代码可以看出来,当Foo函数作为构造函数的时候,其中的this就代表它即将new出来的对象。
而直接调用Foo函数时,其中的this就是window
如果函数作为对象的一个属性时,并且作为对象的一个属性被调用时,函数中的this指向该对象。而不作为函数的属性被调用那么this的值就会之window
1 | var obj = { |
1 | var obj = { |
当函数被call和apply调用时,this的值就取传入的对象的值。
1 | var obj = { |
在全局环境下,this永远是window
1 | console.log(this === window); // true |
普通函数在调用的时候其中的this也都是window,但也有需要注意的情况
1 | var x = 10; |
1 | var obj = { |
执行上下文栈就是一个压栈和出栈的过程,流程如图:
执行全局代码时,会产生一个执行上下文环境,每次调用函数又会产生执行上下文环境。当函数完成时,这个上下文环境以及其中的数据都会被销毁,再重新回到全局上下文环境。
具体参考博客–深入理解javascript原型和闭包(11)——执行上下文栈
es6之前JavaScript没有块级作用域,JavaScript除了全局作用域之外,只有函数可以创建作用域。是因为var
的变量提升,在if
语句或者for()
括号内定义的变量在块外部也可以访问到。但在es6之后用let
和const
声明变量就只在所在的块有效了。所谓的块就是{}
大括号里的语句。
作用域是一个抽象概念类似于一个地盘
作用域有上下级关系,上下级关系确定就看是在哪个作用域下创建的
作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
1 | var a = 10, |
整个执行流程:确定全局上下文环境 -> 在调用fn时,生成fn函数的上下文环境 -> 压栈 -> 并将此上下文环境设为活动状态 -> 执行完之后销毁上下文环境(调用br的时候,也是类似流程)
作用域只是一个抽象感念,类似于地盘,其中没有变量。变量要通过作用域对应的执行上下文环境来获得变量的取值。
作用域中变量的值是在执行过程中产生和确定的,而作用于是在函数创建的时候就确定了。
如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。
1 | var x = 10; |
b的值是可以直接在fn的作用域中去取的,因为b就是在这里定义的。x的取值就需要到另一个作用域中去取。
具体是那个作用域呢,不应该描述为父作用域,有些情况下会产生歧义。应该是到创建这个函数的那个作用域中去取值,而不是调用这个函数的作用域去取值。这就是所谓的静态作用域
。
上段代码描述的只是跨一步作用域去寻找,如果跨一步没有找到就接着跨,一直跨到全局作用域为止。要是全局作用域都没有找到,那就真的没有了。而这一步步跨域的“路线”就是我们所说的作用域链。
取自由变量时的“作用域链”过程:
a
,如果有则获取并结束,如果没有则继续。a
未被定义并结束,否则继续。在MDN中,作者将闭包这样定义:闭包是函数和声明该函数的词法环境的组合
。
具体说起来有两种应用情况:
1 | function fn(){ |
上段代码在执行f1(15)
时用到了fn()
作用域下的变量max
的值。
1 | var max = 10; |
上段代码中,fn
函数作为一个参数被传递进入另一个函数,赋值给参数f
。然而在执行f
的时候,变量max
的取值是10而不是100。这就是因为上一小结所说的查找自由变量的取值时,是从创建这个函数的作用域去找而不是调用这个函数的作用域中去找。
在讲闭包时,除了联系作用域以外还要结合上下文执行栈。
在之前的上下执行栈中讲到有些情况下,当函数调用完之后,其执行上下文环境不会接着被销毁,这就是需要理解闭包的核心内容。
在函数返回一个函数这种闭包中,因为返回的是一个函数,函数的特殊之处在于他会创建一个独立的作用域,而他内部的变量也有可能有自由变量,因此包含返回函数的这个函数的执行上下文是不会被注销的,因为返回的函数需要顺着他之前的这个函数的执行上下文环境去获取这个自由变量。
由于在产生闭包的时候该销毁的执行上下文环境没有被销毁,所以可以很明显的看出闭包会增加内容的开销。
所以闭包和作用域及上下文环境有着密不可分的关系。
总结:
可以理解为一个看不见摸不着的对象(有若干个属性),虽然看不见摸不着,但确确实实存在,因为所有的变量都在里面存储着。
是抽象的。其次除了全局作用域,只有函数才能创建作用域。创建一个函数就创建了一个作用域,无论调用不调用,函数只要创建了,它就有独立的作用域,就有自己的一个“地盘”。
一个作用域下可能有若干个上下文环境,有可能从来没有过上下文环境(函数从来没有被调用过)。有可能用过,现在函数被调用完了,上下文环境被销毁了,也有可能存在一个或多个闭包。再精简一下就是
作用域是静态的组织结构,上下文是动态计算的