JavaScript经典语录
Js的解析与执行过程:
全局中的解析和执行过程:
一:预处理:创建一个此法环境LE,
扫描JS:1.用声明的方式声明的函数(不是函数表达式),2.用var定义的变量。加到预处理阶段的此法环境中
全局环境中的预处理:预处理创建的词法作用域LE相当于windows
二:命名冲突的处理:
1.处理函数声明有冲突时,会覆盖,处理变量声明有冲突时,会忽略。
2.两者都有时,函数声明的权重高一些,最终结果指向函数声明的引用
三:全局函数的执行
1.首先进行全局预处理
2.执行完预处理后,代码一步步解析
补充:运用词法的作用域,可以很好的解释一个带多个参数的函数只传递一个参数的例子。
函数中的解析和执行过程:
函数中的解析和执行过程区别不是很大,有一个(arguments)注意一下:
1.函数的预处理和全局的预处理类似(只是加了一个arguments,调用函数时实际调用的参数个数)
如果不是var 声明的变量,会变成最外部LE的成员,即全局变量
JS的作用域和作用域链:
作用域一般分为四类:块级作用域、函数作用域、动态作用域、词法作用域(静态作用域)
块级作用域:(js没有块级作用域)
函数作用域:没有纯粹的函数作用域
动态作用域:没有动态作用域
词法作用域(静态作用域)javascript的作用域为静态作用域
f[[scope]]==LE==window
分析:1.创建一个作用域对象f[[scope]]==创建它时的词法环境LE(LE==window)
2.真正执行的时候(一步一步往上找)LE--->f.[[scope]]==window
在词法解析阶段,就已经确定了相关的作用域。作用域还会形成一个相关的链条,称为作用域链
new Function的情况又不一样
问题:
多个函数都想要一个变量,每次都要写一个好麻烦
方法:将变量设置为全局变量
问题:
不是说要减少全局用量的使用么?因为在我们做大项目的时候难免要引入多个JS库,变量间的命名可能会有冲突,且出错后不易查找,这个时候我们该怎么办呢?
方法:将变量设置在一个function中,
问题:
在外面又访问不到了,怎么办?
方法:我们使用匿名函数的方法
Javascript中的作用域链:
当执行一段JavaScript代码(全局代码或函数)时,JavaScript引擎会创建为其创建一个作用域又称为执行上下文(Execution Context)
在页面加载后会首先创建一个全局的作用域,然后每执行一个函数,会建立一个对应的作用域,从而形成了一条作用域链。
每个作用域都有一条对应的作用域链,链头是全局作用域,链尾是当前函数作用域。
作用域链的作用是用于解析标识符,当函数被创建时(不是执行),
会将this、arguments、命名参数和该函数中的所有局部变量添加到该当前作用域中,
当JavaScript需要查找变量X的时候(这个过程称为变量解析),它首先会从作用域链中的链尾也就是当前作用域进行查找是否有X属性,如果没有找到就顺着作用域链继续查找,直到查找到链头,也就是全局作用域链,仍未找到该变量的话,就认为这段代码的作用域链上不存在x变量,并抛出一个引用错误(ReferenceError)的异常。
1.JavaScript中没有块级作用域,但是有词法作用域
函数内部不用var关键字申明的变量,则默认该变量为全局变量
在javascript中如果不创建变量,直接去使用则报错
2.javascript中如果创建值而不赋值,则该值为unefined
javascript 的函数在被执行之前,会将其中的变量全部申明而不赋值
3.词法作用域是不可逆的
闭包函数的由来:
作用域的存在帮我们省不少事,如果想在函数A中调用函数B该怎么办?
思路:我们给函数B设一个返回值,然后在函数A中调用,代码如下:
function A(){ function B(){ console.log("Hello foodoir!"); } return B; } var c = A(); c();//Hello foodoir!
这样我们就可以得到我们想要的结果。这样,我们基本上到了一个最简单的闭包形式。我们再回过头分析代码:
(1)定义了一个普通函数A
(2)在A中定义了普通函数B
(3)在A中返回B(确切的讲,在A中返回B的引用)
(4)执行A(),把A的返回结果赋值给变量 c
(5)执行 c()
把这5步操作总结成一句话:函数A的内部函数B被函数A外的一个变量 c 引用。当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。
来看下面的几种闭包:demo1:
function fn(){ var b = "foodoir"; return function(){ console.log(b);//foodoir return b; } } //console.log(b);//b is not defined var result = fn(); console.log(result());//foodoir
demo2:
var n; function f(){ var b = "foodoir"; n = function(){ return b; } } f(); console.log(n());//foodoir
demo3:
//相关定义与闭包 function f(arg){ var n = function(){ return arg; }; arg++; return n; } var m = f(123); console.log(m());//124 //注意,当我们返回函数被调用时,arg++已经执行过一次递增操作了,所以m()返回的是更新后的值。
demo4:闭包中的读取与修改
//闭包中的设置与修改 var getValue,setValue; (function(){ var n = 0; getValue = function(){ return n; }; setValue = function(x){ n = x; } })(); //console.log(n); console.log(getValue());//0 console.log(setValue());//undefined setValue(123); console.log(getValue());//123
demo5:用闭包实现迭代效果
//用闭包实现迭代器效果 function test(x){ //得到一个数组内部指针的函数 var i=0; return function(){ return x[i++]; }; } var next = test(["a","b","c","d"]); console.log(next());//a console.log(next());//b console.log(next());//c console.log(next());//d
demo6:循环中的闭包
//循环中的闭包 function fn(){ var a = []; for(var i=0;i<3;i++){ a[i] = function(){ return i; } } return a; } var a = fn(); console.log(a[0]());//3 console.log(a[1]());//3 console.log(a[2]());//3 /* * 我们这里创建的三个闭包,结果都指向一个共同的局部变量i。 * 但是闭包并不会记录它们的值,它们所拥有的只是一个i的连接,因此只能返回i的当前值。 * 由于循环结束时i的值为3,所以这三个函数都指向了3这一个共同值。 * */
思考:如何使结果输出分别为0、1、2呢?
思路一:我们可以尝试使用自调用函数
function fn(){ var a = []; for(var i=0;i<3;i++){ a[i] = (function(x){ return function(){ return x; } })(i); } return a; } var a = fn(); console.log(a[0]());//0 console.log(a[1]());//1 console.log(a[2]());//2
思路二:我们将i值本地化
function fa(){ function fb(x){ return function(){ return x; } } var a = []; for(var i=0;i<3;i++){ a[i] = fb(i) } return a; } console.log(a[0]());//0 console.log(a[1]());//1 console.log(a[2]());//2
在这里,我们来对闭包进行更深一步的操作
我们再将demo1的例子进行扩展
代码示例如下:
function funcTest(){ var tmpNum=100; //私有变量 //在函数funcTest内 //定义另外的函数作为funcTest的方法函数 function innerFuncTest( { alert(tmpNum); //引用外层函数funcTest的临时变量tmpNum } return innerFuncTest; //返回内部函数 } //调用函数 var myFuncTest=funcTest(); myFuncTest();//弹出100
到这,我们对闭包的概念和用法有更加熟悉
闭包和this相关
闭包应用举例,模拟类的私有属性,利用闭包的性质,局部变量只有在sayAge方法中才可以访问,而name在外部也访问,从而实现了类的私有属性。
function User(){ this.name = "foodoir"; //共有属性 var age = 21; //私有属性 this.sayAge=function(){ console.log("my age is " + age); } } var user = new User(); console.log(user.name); //"foodoir" console.log(user.age); //"undefined" user.sayAge(); //"my age is 21"
关于闭包更深入的了解
(function(document){ var viewport; var obj = { init:function(id){ viewport = document.querySelector("#"+id); }, addChild:function(child){ viewport.appendChild(child); }, removeChild:function(child){ viewport.removeChild(child); } } window.jView = obj; })(document);
这个组件的作用是:初始化一个容器,然后可以给这个容器添加子容器,也可以移除一个容器。功能很简单,但这里涉及到了另外一个概念:立即执行函数。 简单了解一下就行。主要是要理解这种写法是怎么实现闭包功能的。
闭包并不是万能的,它也有它的缺点
1、闭包会使得函数中的变量都保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页性能问题。另外在IE下有可能引发内存泄漏 (内存泄漏指当你的页面跳转的时候 内存不会释放 一直占用你的CPU 只有当你关闭了浏览器才会被释放);
2、闭包会在父函数外部改变父函数内部的变量的值,所以不要随便改动父函数内部的值。