一、回调函数
二、IIFE
三、函数中的this
四、关于语句分号问题
五、原型与原型链
六、探索instanceof
七、执行上下文
八、作用域与作用域链
一、回调函数
二、IIFE(立即调用函数表达式)
也可用于编写 js 模块
var a = function (){ console.log("a"); } function b(){ console.log("b"); } (function c(){ //匿名函数自调用这个和IIFE其实是同一个概念的 console.log("c"); })()
最后打印出来的 只有 c
那就有一个疑惑了,为什么不能直接把这个匿名函数的内部代码直接在外面写了,而是要用这个“包”起来呢?
因为包起来的话,就是一个局部了,里面的变量就是一个局部变量,不会影响全局变量
(function d(){ var a = 1; function test(){ console.log(++a); } function test2(){ console.log("test2"); } window.$ = function(){ return { test: test } } })(); $().test()
这种的话,就是把test向外暴露了,暴露为一个全局函数,但是test2没暴露出来,这也是匿名函数的一个功能了
其中不要被$里面了,在匿名函数d中,我们给这个$定义了一个函数,$其实就是一个函数,这个函数执行之后,就会返回一个对象
这个对象的名字叫做test,然后就可以通过test()来调用这个test函数了
三、函数中的this
扩展:函数call
https://www.w3school.com.cn/js/js_function_call.asp
<script> //函数外面全局的this就是window function Person(color){ console.log(this);//这个this不是Person,它代表的是谁来调用这个Person this.color = color; this.getColor = function(){ console.log(this); return this.color; }; this.setColor = function(color){ console.log(this); this.color = color; }; } Person("red"); //这个时候就输出语句一个,this是window,因为没人调用,就是用window直接调用了 var p = new Person("yello");//这个时候的this 是 Person,也可以说是p,因为p指向这个对象 p.getColor();//这个时候的this 是 p var obj = {}; p.setColor.call(obj , "black");//这里的this是obj,把obj打印出来就是一个对象,有一个color属性值为black // console.log(obj); var test = p.setColor; test();//这个时候的this是 window,因为test是一个函数,没东西调用,就是window调用 //全局调用的话this都是window function fun1(){ function fun2(){ console.log(this); } fun2();//this是window } fun1(); </script>
四、关于语句分号问题
一般在做项目的时候,如果用到很多js文件的话,都是在这个文件开始的时候添加上一个分号,防止代码合并的时候会出错的
五、原型与原型链
1、原型prototype
下面是打印 Date.prototype 的结果
可以看到是一个object类型的,然后里面有很多内置的方法,这些方法都是给实例对象使用的
如果是这样打印的话,就是一个龙的object,但是我们可以人工的给这个函数原型添加方法
②
返回了两个 true
也就是说构造函数和它的原型对象是有一种相互引用的关系,也就是构造函数的一个叫做prototype的属性可以找到这个构造函数的原型
并且这个构造函数的原型里面也有一个属性 叫做 constructor 可以找到这个构造函数本身
2、显示原型和隐式原型
<script> function Fn(){ }; console.log(Fn.prototype); var fn = new Fn(); console.log(fn.__proto__);
原因就是第三句话,要验证的话,直接用===来判断即可,prototype和__proto__共同指向原型对象,都是引用值,保存 地址值的
函数的prototype这个属性是在函数创建的时候就写入了,而__proto__这个属性是在实例对象创建的时候生成的-new的时候(都是js引擎自动帮我们加的)
new Fn()也就是这个创建这个函数对象的时候,内部执行了一个赋值就是把这个函数的prototype属性的值赋值给了这个实例对象的__proto__属性的值
function Fn(){}的时候就是一个创建函数对象的时候,就是给了这个函数的prototype属性赋值为了一个空的object
这个图的左边是一个栈,右边是堆
1、function Fn(){} 定义了一个函数对象(放在堆里面),然后这个函数名Fn放在栈空间里面
其中Fn:0x123,表示的是他存了一个值(地址值)是ox123,也就是说在堆中这个函数对象的地址就是0x123
prototype是一个引用变量属性,他的值是地址值,他指向的是一个原型对象 { }
===第一条语句 执行之后,绘制的图是
2、console.log(Fn.prototype)输出的就是这个对象了
3、 var fn= new Fn()这个fn也是一个引用类型,在栈中fn的值就是一个地址值了
4、最后的fn.test其实是,以为fn.的话有一个点,就会找到Fn实例对象:
但是这个对象里面没有这个test方法,但是最终还是找到了,所以说它的原理就是
如果在 fn 。也就是实例对象中找不到这个方法的话,就会根据这个实例对象的
__proto__属性来找到原型对象object,再在这个原型对象里面找这个test方法
5、原型链
即使是后面才给这个函数添加test2方法的,但是在那之前就打印出来了,因为我们拿到的是最总的函数)
这个空的Object也是object的一个原型
Object本身就是存在的,并且是全局的,一开始就有的,
先有了原型才有实例对象
1、第一个语句
function Fn(){
this.test1 = function() {
console.log("test1()")
}
这个语句 就画了下面这个图片了
上面的Object 那三个图,都是一上来就有的所以js引擎一开始的时候是把js内置的一些函数和方法加载进来了
补充:
4、原型链-属性问题
==前面都是给这个函数添加方法,1这里就是给这个函数添加属性
这两个输出的都是 xxx ,但是打印fn2.a的时候就会显示yyy了
===为什么后面通过 fn2.a=‘yyy’没有把前面的xxx覆盖掉了呢
我们把修改之后的fn2打印可以看到
自身多了一个a属性,然后原型也有一个a属性的
注意:原型链是在查找的时候用到的,但是在设置原型链的时候是不看的
六、探索instanceof
如果是B的话就走一部,也就是找prototype的显示原型属性,如果是a的话就可以走很多步,但是每一步都要走隐式原型属性的__proto__
面试题:
它的思路是,先生成了一个 n==2,m==3的对象,之后再赋值给a的原型的,并不是直接修改原来a原型指向的东西
因为a的原型保存的是地址值,所以就不是一个东西了,因为地址改变了,指向就不同了4
所以结果就是:b.n==1 b.m=undefined c.n==2 c.m==3
第一个f.a可以和object本身就有的 toString 方法类比,因为直接对了object的原型添加了方法和属性,那么f就可以直接访问它们了
这个a属性和tostring是放在一个容器里面的
这个就是打印出来的f,也就是原型链了,因为找到proto的时候是找到了这个F函数,但是里面没有a,所以再找下一个proto就找到了Object的原型,就可以找到a这个属性了
但是f.b是找不到的,就会报错了
上面这个部分就比较重要了
其中的除了 f.b()是会报错之外(因为找不到这个属性),其他的都是可以执行打印出来的
七、执行上下文
执行上下文栈
这个代码其实是没问题的,
因为在bar里面的调用f00的时候,其实下面的foo函数定义已经执行了,因为函数定义执行并不待变前面的foo的调用已经执行了
就是前面的bar和foo是两条赋值语句,并不代表函数就执行了,最后面才调用了bar函数的
在上面那个函数中 产生了三个执行上下文对象,一个是window作为全局执行上下文 两个函数对象function
(函数被调用的时候就会产生函数执行上下文对象,定义的时候是没有产生的)
也就是
这两个语句执行的时候,就会产生函数的执行上下文了
所以执行上下文栈中的话,就是n+1层,其中n就是函数被执行的语句(调用函数的次数),1就是window产生的全局执行上希望了
同理:
如果代码是这样的话,执行上下文栈中就有5层了,因为一个bar调用然后引发一个foo调用,两个bar的话,就有4词被调用了,再加上wondow全局调用
一个函数被调用,就会在执行上下文栈中添加,然后执行完了之后,就会出栈的了,就被释放出来了(正在执行的都是栈顶的对象的)
也就是说全部的代码都执行完了之后, 执行上下文栈中留下的就只有 window了
那么栈底有没有可能不是window呢?--不可能,因为第一个产生的就是window(全局执行上下文和当前执行上下文均只能由一个,其他都是要进行等待的了)
(小扩展)如果用f1里面调用f2和f3的话,执行上下文栈中最多由三个执行上下文,因为在f1中调用f2的时候,不会调用f3,当执行晚了f2之后才会调用f3,所以同时最多的话只能是3个了
面试题:
面试题:
1、
输出的要么是undefined 要么就是 function==这里就涉及到了变量提升和函数提升顺序了
====先执行变量提升,再执行函数提升
所以最终打印出来的就是 function 了
2、
在就涉及到,在if之前对b进行了声明定义,还是在先if再对b声明了
实际上的代码实现是这样的
所以说就会报错了,因为c不是函数来的,(先是变量提升之后才是函数提升)
也就是c是变量了,c(2)的话就会报错了,也就是说 定义的函数c里面的代码都是没有执行的
八、作用域与作用域链
1、块作用域就是在 大括号里面的作用域了
这里有几个作用域呢?
外面的全局作用域+定义了几个函数
注意:
这里的b其实,会打印20的,因为b是全局的,在里面也是可以访问到的
因为b是先在这个函数里面找的,如果没有的话,就去外面找的
2、作用域 与 执行上下文
作用域链 就是嵌套的作用域 由内向外的一个过程
相关面试题:
打印出来的答案 是 10
因为相当于是直接调用了fn,也就是要在fn里面找有没有x,如果没有的话,就在外部作用域里面找了,(注意 并不是在show函数里面执行,而是会回到上面的fn作用域中执行的)
面试题二:
如果是输出fn的话,就会吧赋值给fn的这个函数打印出来了
在输出fn2的时候,开始在函数里面找不到,所以在外部作用域里面找,想要找到fn2的话,就是要找obj这个对象里面找才行,也就是this.fn2才行
所以在外面是找不到的
就是在沿着作用域找的时候没找到
但是如果打印this的话就可以找到了