【学习笔记】深入理解js原型和闭包系列学习笔记——精华
深入理解js原型和闭包笔记:
1.“一切皆是对象”,对象是属性的集合。
丨 函数也是对象,但是使用typeof时为什么函数返回function而
丨 不是object呢,js为何要对函数做这样的区分?函数和对象到底什么关系呢?
2.函数和对象的关系(对象是函数创建的,但同时函数又是一种对象)
丨 对象是通过函数创建的,那它的属性是如何通过函数构造的,又为什么说函数自身也是一种
丨对象呢,函数有什么对象的特征(对象的特质是属性的集合),难道函数也是属性的集合?它
丨有什么属性呢?
3.函数的属性:prototype原型(函数也是对象,都有一个叫做prototype原型的属性,这个属性的属性值也是一个对象,该对象中又默认包含一个constructor的属性,指向函数本身。函数所创建的对象所具有的属性定义来源于‘Fn.prototype.对象属性’)
丨但是我们在使用对象的属性时都是‘对象.属性名’这种方式,为什么不是 ‘对象.prototype.属性名’呢,
丨明明属性名是定义在prototype下的,原来对象都有一个隐藏原型 _proto_,‘对象._proto_’=== Fn.prototype 。
4.隐藏原型(每一个对象都有一个隐藏原型,指向创建该对象的函数的prototype ---别忘了每一个函数都有一个prototype属性哦)
丨那么问题来了,上面说函数的prototype属性值也是对象,那它的_proto_指向哪,prototype属性值对象是被Object函数创建的,
丨它的_proto_指向Object函数的prototype,那Object函数的prototype属性值的_proto_指向哪呢,Object函数的prototype
丨属性值的_proto_指向null。还有一个问题,上面函数也是对象,那函数的_proto_指向哪呢,函数是由Function函数创建来的,
丨所以函数的_proto_指向Function函数的prototype。那Function函数的_proto_指向哪呢,答案是指向Function自身的prototype,
丨因为Function被自身所创建,这也形成了一个环形引用。(只需要记住一个准则,对象的_proto_指向创建它的函数的prototype)。
丨是不是很乱,吼吼,一点也不乱。要是你和我一样都喜欢整洁干净感觉还乱,那么接下来我们借助instanceof来帮助我们理清
丨一下js内部“混乱的关系”。(下图为普通函数、Function及Object之间的关系图,这张图就是js原型链继承关系核心了,我们1~7
丨其实就是在讲解理清这张关系图)。
5.instanceof(A instanceof B的判断规则是:沿着A的_proto_这条线来找,同时沿着B的prototype的这条线来找,如果这两条线能找到同一个引用,即同一个对象,那么就返回true,如果找到终点还未重合,则返回false。Instanceof表示的是一种继承关系,或者原型链的结构。这时再对着图理一理是不是更加清楚了,原来他们指来指去就是在构造这样的继承关系)。
丨Instanceof这样设计,是想表达什么,是想表达我们接下来介绍的东东——继承——原型链。
6.原型链和继承(访问一个对象的属性时,先在基本属性中找,如果没有,在沿着_proto_这条链向上找,这就是原型链,而这种获取上级属性的方式就是继承)。
| 上面说了这么多函数、对象、原型、隐藏原型,以及之间如何相互指向构成js的继承和原型链的,那么我们也说一下这种原型的优
丨势——灵活性。
7.原型的灵活性(对象属性可以随时改动随时添加)如果你要添加内置方法的原型属性,最好做一步判断,如果该属性不存在则添加。如果本来就存在,就没必要添加了。
-----------------------------------------------------------------------------------------------------------------------------------
呵呵呵呵,终于看到分割线了,没错上面结束了。1~7介绍的就是js中的原型,从函数与对象的关系说起,介绍了js是如何通过函数和对象的原型、隐藏原型建立js内部的原型链和继承机制的。
8.【执行上下文环境】上(在执行代码之前,要把用到的所有变量先拿出来,有的直接赋值,有的先用undefined占个空)。先来介绍全局作用域的上下文都有哪些内容。
a.变量和函数表达式——变量声明,但是不赋值,先用undefined占空;
b.this——赋值;
c.函数声明——赋值,赋值函数体的内容; 变量提升函数提升
丨那么函数体中的执行上下文环境有哪些呢?走起
9.【执行上下文环境】下(函数每被调用一次,都会产生一个新的执行上下文环境,那么产生的上下文环境都有什么呢,看下面)。
a.参数——赋值
b.arguments——赋值
c.自由变量的取值作用域——赋值(函数在定义的时候不是在调用的时候就已经确定了函数体内自由变量的作用域了)。至于“自有变量”和“作用域”,后面专门会讲。
丨那么我们又有了新的问题,在js执行的时候,会有数不清的函数调用,会产生很多个函数上下文环境,这么多上下文环境
丨该如何管理,以及如何销毁这些内存呢,我们接下来讲解“执行上下文栈”。不过在讲解“执行上下文栈”之前,我们先讲解一下this,
丨this还是挺重要的。
10.this(this到底取何值呢,this的取值是在函数真正被调用执行的时候确定的,函数定义的时候确定不了。(而blog中仅仅是使用例子介绍了this取值的四种情况,难道又要死记硬背吗?具体原因却讲解的不够清楚,这完全不符合我们的宗旨。我的另一篇blog详细介绍了this取值的前因后果,说白了就是指向调用它的对象,但是这样说不是很精确,具体参考blog:https://www.cnblogs.com/lauzhishuai/p/9494196.html。在此我将这篇文章的精髓整理一下,让你彻底知道同时又理解this到底是如何取值的)。
一:
一句话:this的指向在定义时是确定不了的,只有在执行的时候才能确定this的指向,this指向最终调用它的对象(在被多个对象一层层调用出来时,this指向他上一级的对象,而不是最外层对象)。
这句话可以拆分成四句通俗易懂的话:如果函数中有this,且在调用函数时
a.首先如果调用函数时没有什么东西"."出他来,函数中的this指向Window,指Window.在严格模式下this指向的是undefined
b.如果只有一个对象“.”出函数来,函数中的this指向这个对象。(A.fn(),fn()中的this指向A)
c.如果通过多层对象一步步“.”出函数(A.B.fn()),那么函数中的this指向紧邻的上一级,此处即为B
d.注意一定是最终,即最后“.”出函数调用的那一行代码中去找上面三种情况。
另外还有几种其他使用情况情况:
一:
构造函数中:构造函数中的this指向它所创建的对象。(原因:new操作符创建一个新对象,然后调用apply将this指向了这个新对象)
二:
函数使用apply、call、bind方法自行改变this的指向。
1.call/apply:当函数通过使用原型链中继承自Function 对象的call和apply方法调用执行时,函数内部的this绑定到call和apply方法的第一个参数对象上(如果第一个参数不是对象,那么先将其转换成对象,然后绑定)
1 function add(c, d){ 2 return this.a + this.b + c + d; 3 } 4 5 var o = {a:1, b:3}; 6 7 add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16 8 9 add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
1 function tt() { 2 console.log(this); 3 } 4 tt.call(5); // Number {[[PrimitiveValue]]: 5} 5 tt.call('asd'); // String {0: "a", 1: "s", 2: "d", length: 3, [[PrimitiveValue]]: "asd"}
2.bind :bind方法返回一个函数,通过bind方法返回的函数this将永远绑定在bind方法的第一参数对象上,无论其后面在什么情况下被调用。
1 function f(){ 2 return this.a; 3 } 4 5 var g = f.bind({a:"azerty"}); 6 console.log(g()); // azerty 7 8 var o = {a:37, f:f, g:g}; 9 console.log(o.f(), o.g()); // 37, azerty
三:
DOM 事件处理函数中的this指向触发处理函数的标签。
元素内联事件中的this:
1.当代码被内联函数调用时,this指向监听器所在的DOM元素。
2.当代码被包含在其他函数中执行时,this 的执行等同于函数直接被调用的情形(非严格模式下为Window,严格模式下为undefined)
四:延迟函数setTimeout和setInterval 中的回调函数内部的this指向window。
五:
另一种特殊情况:在碰到retrun时,如果返回的是一个对象,那么this指向的就是这个对象,如果返回的不是一个对象,那么this指向函数的实例。(注意在返回null时,虽然null是对象,但是此时this指向函数的实例)。
-----补充知识点:undefined是基本数据类型,表示未定义。null是一个对象表示空对象。
六:箭头函数中的this
箭头函数不会创建自己的this,它只会捕获其作用域链中上级作用域中的this。且这个this的指向时不可以改变的。
1.call/apply/bind 方法并不能改变箭头函数中this的指向。
2.是否严格模式对箭头函数中的this没有影响。
好了this就这么多,是不是一目了然了,this搞清出了让我们继续撤回我们本系列的主题,继续开始我们的闭包之旅。
11.执行上下文栈(就是一个压栈出栈的过程,保持活跃的只有一个执行上下文环境)
丨理想的执行上下文栈很简单,但是有一种很常见的情况并不能做到如此完美的压栈出栈,说销毁就销毁,这就是我们接下来要讲的——闭包,
丨但是在讲之前我们还要从“自由变量”和“作用域”说起。
12.作用域(学过编程的都知道作用域,这里需要注意几点就行了:a.js中没有块级作用域,同时js除了全局作用域外只有函数可以创建作用域。那么我们在编程的时候,定义变量时最好只在全局代码前端和函数开头定义,以免产生混淆;b.作用域之间存在父子关系,这是下面的作用域链了,也没什么;c.作用域最大的作用就是隔离变量)
丨好了,这节没有什么可讲,我们继续,下面我们将作用域和执行上下文环境结合起来讲。
13.作用域和执行上下文环境(作用域只是一个“地盘”,是抽象的概念,没有变量,要通过作用域对应的执行上下文环境才能找到变量的取值,作用域在定义的时候已经确定了,而执行上下文只有在具体执行时才产生确定)。
|好了上面是作用域和执行上下文之间简单的关系,其实都理解,下面再讲讲跨作用域取值——即“自由变量”和作用域链
14.自由变量和作用域链(自由变量:在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。而如果在当前作用域中找不到自由变量的取值,那么就到定义该函数的上一级作用域中找,注意这里是定义而不是调用——其实这就是所谓的静态作用域。而如果在找不到则继续到它的上上创建父级作用域找,而这个寻找过程就产生了所谓的作用域链)。
丨好了,我们的一切知识都已储备好,终于轮到我们的“闭包”出场了,其实8~14讲的执行上下文环境、执行上下文栈、作用域、自由变量对于
丨学过编程的人来讲基本上都是知道的,它就是你理解的那样,可能js中有几点特殊需要注意罢了,所以没有必要觉得“闭包”多厉害,因为基础
丨东西你早就懂了。
15.闭包(这篇系列的文章闭包讲的有点啰嗦,他结合上面的作用域链和执行上下文栈讲解了闭包,确实是这样,不过上面说的是原因,我们其实只需要一句话就可以知道闭包是什么,其中的变量如何取值:闭包就类似于一个背包,当函数作为返回值或者函数参数时,函数中包含的变量取值也会带过去,就像一个背包一样带着他们,它们的取值要到函数原来对应的作用域链中找)。
-----------------------------------------------------------------------------------------------------------------
好了,终于讲完了,js中的原型、闭包、this是不是现在理解的很透彻了。看完这篇总结,本系列文章中后面两篇的补充就没必要看了,在总结中早就含括在内了。这些知识是不是很简单,并没有你想想中的那么难,网上的很多资料杂七杂八,讲解的时候要不就是讲解的思路不清晰,要么就是只讲片面的知识点,本系列文章“小步快跑”,环环紧扣,很有逻辑和黏性的把所有的东西都讲解透了,对于不理解相关知识的人还是很值得看的。溜了溜了~~