当前位置:首页 > 网站技术 > 前端技术 > 正文内容

JS 预解析机制与定义函数的三种方式

小彬2020-06-17前端技术246

1、JS 预解析机制与定义函数的三种方式 

定义函数的三种方式


1、 函数声明(用的最多,推荐优先使用)

  function fn(){
   }
   fn()//直接调用


2、 函数表达式(也叫匿名函数)

  var fn=function([参数]){
   //把函数赋给一个变量,函数并没有真正的名字,所以叫匿名函数
   }
   fn()


3、 自执行函数 (自己执行自己,并且在声明的同时就调用自己,只能调用一次)
(1)第一种写法

(function(){
    console.log('自执行函数')
 })()



这样写的原理是:他是由函数表达式演变过来的,var fn = function(){} fn(); 把 fn 替换成:
function(){},就成了: function(){}(); 为了保持整体性,在 function 加上一个(),所以就变成
了 (function([这里是形参]){})([这里是实参])

(2)第二种写法

(function(){
    console.log('自执行函数')
 }())



值得注意的是,用这种写法会有一个小漏洞,看个例子:

var fn =function(){
    console.log('123')
 }
 (function(){
   console.log('456')
 }())


输出结果是:
456
123
这里匿名函数 fn 明明没调用,为什么会输出?看下面简化后的例子:

<script>
    var fn = function(){
      console.log(123);
    }(function(){
      console.log(456)
    }())
  </script>


可以很明显的看到(function(){console.log(456);}())这个自执行函数调用输出了 456,同
时,这个自执行函数跟在匿名函数的后面,JS 解析的时候把这个匿名函数也解析成为了自
执行函数,所以就输出了 123,解决的办法很简单,在第一个匿名函数的后面加一个分号“;”
或者直接采取第一种写法就能避免这种问题

JS 预解析机制


在当前作用域下,js 运行之前,会把带有 var 和 function 关键字的事先声明,并
在内存中安排好。(这个过程也可以理解为变量提升)然后再从上到下执行 js
语句。预解析只会发生在通过 var 声明的变量和 function 声明的函数上。

1、预解析过程:
(1)把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
(2)把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。
(3)先提升 var,再提升 function
因此,我们在定义函数时,绝大多数都使用第一种方式(函数声明的方式)

var
使用 var 定义的变量预解析:告诉解析器知道有这个名字的存在并默认将该变量
赋值 undefined ,如下:

console.log(x) //undefined
 var x=15


变量 x 虽然是在 console.log 后面定义的,但是使用 var 声明的 x 会提前保存在
内存中,并赋值 undefined ,然后再从上往下执行 js 语句 。它的执行顺序类
似于如下:

var x
 console.log(x) //undefined
 x=15


先声明了 x ,x 没有赋值,默认赋值为 undefined ,输出的结果自然为
undefined 。然后再给 x 赋值为 15。
需要注意的是,如果变量声明没有使用 var ,不存在变量提升的。如下:
x 没有使用 var 声明,所以报错找不到 x。

console.log(x) //error :x is not defined
 x=15


当匿名函数多用于定时器里面和注册事件的时候,比如

<script>
btn.onclick = function(){
console.log("这是一个匿名函数")
} 
</script>

 

2、预解析示例:

<script>
var num=789
function fn(){
    console.log(num)
    var num =789
}
fn();//调用函数
 </script>

此时控制台打印的是 undefined,原因是:JS 解析代码时,把函数的声明还有变量的声明
提升到当前作用域的最前面,所以代码就变成:

<script>
var num;
num=789
function fn(){
  var num;//从这里可以看出num只是申明而没有定义赋值
    console.log(num)
     num =789
}
fn();//调用函数
 </script>

 

 

 

2.、逐行解读代码

声明变量如果没有赋值,则js自动赋值undefined

var a; =>等同于var a=undefined

然后查找function,找到函数,直接拿过来(也就是说在预解析的时候,函数就已经声明了)
在逐行解读代码时,var有值的就赋值,函数可以直接跳过。


预解析问题
1、变量名冲突  预解析都是undefined,只有解读代码时会覆盖
2、变量名和函数名冲突,变量名舍弃,保留函数名
3、函数名冲突,谁在后面就留谁
4、不要在代码块里定义函数
5、预解析时,函数里面带有参数的时候,对参数不使用var,那么它只是局部变量

 2、变量

基本类型

引用类型

不可修改

可以修改

保存在栈内存中,固定大小空间

保存在堆内存中,大小空间不固定

按值访问

按引用访问

比较时,值相等即相等

比较时,同一引用才相等

复制时,创建一个副本

复制的其实是指针(指向同一个对象)

按值传递参数

按值传递参数

用typeof检测类型

用instanceof检测类型


字符串方法都不是在原来基础修改,都会返回一个新值

基本类型使用方法都会找到对应的包装对象,包装对象的属性借给基本类型,这样就能修改基本类型的值

比如1->number  'aac'->string

引用类型都是在原来基础修改

引用类型的三种比较方法

第一种方法:第一个引用的值赋值给第二个引用,这才算同一个引用

<script>
var xm= {
  age:18,
  score:4
};
var xh=xm;
console.log(xm === xh)
</script>

 

 

第二种方法:通过遍历或者循环比较每一项,如果每一项都相等,那就是相等的引用类型

<script>
var xm= {
  age:18,
  score:4
};
var xn= {
  age:18,
  score:4
};
function equaObj(a,b){
  for(var p in a){
      if(a[p] !==b[p])return false
  }
  return ture
}
console.log(equaObj(xm,xn))
</script>

第三种方法,数组的比较

<script>
var xm= {
  age:18,
  score:4
};
var xn= {
  age:18,
  score:4
};
function equaArray(a,b){
 if(a.length !==b.length)return false;
  for(var i=0;i<a.length;i++){
      if(a[i] !==b[i])return false
  }
  return ture
}
console.log(equaObj(xm,xn))
</script>

 

引用类型的复制

因为指向的是同一个引用,所以改变新的引用旧的也会发生改变,

1、创建一个函数,对原引用类型进行循环遍历,输出得到的就是独立的一样的引用类型(浅拷贝)

 

2、递归函数,递归原引用类型所有值给新的引用类型,就是深拷贝

如果能将原引用类型的对象也能拷贝,就叫深拷贝,

---à

 

2、形参和实参

形参和实参个数,
当形参等于实参时,也是最常见的方法
当形参大于实参时,
1.如果有可选参数(默认参数),就可传可不传
jq的$('',document.getElementById(''))
第二个值就是默认值,如果没有则是在上下文取,如果有,就获取在该元素里的标签
2.如果没有默认参数,缺少的用Null或者空值代替
当形参小于实参时,借助arguments来获取参数个数
arguments是类数组,保存的是实参的值,不能使用数组的方法,是每个函数独有的
不要使用arguments随意改变形参,会影响参数的输出
argument的值与形参一一对应,如果改变arguments的值,形参也要改变
arguments.callee指代函数本身(严格模式下失效),在需要变化函数名的函数里面使用函数本身的时候,可以使用该方法。
替代方法是把函数赋值给name,函数本身使用一个小名,这样无论函数怎么变化,函数内部都能调用函数,如下图

var factorial function fn(num){
    if(num<=1)return 1;
    return num*fn(num-1)
}
console.log(factorial(5))
arguments.length是实参的个数,fn().length是形参个数

JS 预解析机制与定义函数的三种方式

引用类型的参数传递。上图的打印是xm,因为在函数setName里,obj={}是另外一个引用类型,和形参obj没有关系,所以输出的是xm


什么可以做参数

基本类型和引用类型
数字,字符串,Null,undefined,布尔值
对象,数组,函数(回调函数,满足某种条件的时候才会调用)


函数的输出  返回值  
return  函数中返回函数值
break 循环中,符合条件直接跳出循环
continue 循环中,跳出本次循环接着下次循环

return 后不接值的时候是提前退出函数


alert打印的是()里内容的.string方法
return [],返回的是多个值,类似数组
return 对象,返回对象

 

 

 

 

作用域

局部作用域速度比全局作用域快
所以jquery就是把window当做参数传给局部作用域

使用withj可以延长作用域链(鸡肋,不推荐使用)
 with(person){
 name:''
 }
 修改name的值等于修改person.name

3、函数的定义、调用、传参和返回值

函数的定义

字面量

构造函数

赋值表达式

function fn() {}

var fn = new Function();

var fn = function () {};

函数声明

赋值语句

赋值语句

简洁,直观

多此一举,效率低下

简洁,直观

声明提前

必须在调用语句之前

必须在调用语句之前

 

查找对象的值:cat.name
查找字符串p的值:cat['name'] 
查找name(随意)的值:cat.name  
删除对象的值:delete cat.name
检测对象是否有某个值:'name' in cat,返回boolean值

函数的调用

普通函数

匿名函数

构造函数

方法

间接

fn()

(function () {})()

new Object()

Obj.method()

fn.call()

 

间接调用
每个函数都有call、apply方法
person.getName.call(window),调用的是window下的值。因为call改变了this指向为window,所以调用的是全局的变量。

apply(window),apply为一个参数的时候是和call一样的。
apply(window,[‘’]) ,第二个参数是数组,可以通过数组方式传参,也可以拿来继承函数

 

函数作为参数传递时,可以使用函数名传递,因为函数名fu就代表函数本体,如果是fu(),函数就执行
递归调用:计算阶乘
方法调用,只能调用合法标识符方法,如果出现不合法的,可以用["]
模拟点击事件document.onclick()
链式调用要在方法里返回this指向,这样才能保证后面的方法可以使用

函数的参数

形参

实参

函数定义时括号内的参数

函数调用时括号内实际传递的参数

接收实参的值

将值赋值给形参

个数为fn_name.length

个数为arguments.length

 

函数的返回值

return

continue

break

用在函数中

用在循环中

用在循环中

跳出函数

跳出本次循环

跳出当前循环

 



4、JavaScript 内存管理与垃圾回收机制

为什么要进行垃圾回收?

因为程序中存在很多数据 , 这些数据在内存中占据一定的空间 。在程序运
行中 ,一些没有用的数据(这些数据可以称为垃圾)还会在内存中占据空
间。如果不进行垃圾回收的话,随着程序的运行,可用的内存越来越小
必然会带来程序性能的下降,造成卡、慢甚至系统异常。

js 垃圾回收机制

Javascript 具有自动垃圾回收机制,也就是说,执行环境会负责管理代码执
行过程中使用的内存。
原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释
放其内存。

标记清除

js 中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函
数中声明一个变量,就将这个变量标记为“进入环境”。此时的变量在函数
执行过程中一直存在,直到函数结束后,将变量标记为“离开环境”,变量
就被回收了。(JavaScript 中全局变量的在浏览器卸载页面才会被销) 

如下例子:

function count(){
    var  num=0
   num++
  console.log(num)
}
count();//1
count();//1

定义一个函数 count ,函数 count 被调用了两次结果 num 都输出 1 ,说
明当第一次执行之后函数里面的变量 num 会被回收。然后在第二次调用时
又重新声明变量 num 并初始化为 0,再进行计算输出结果 1。

引用计数(不常用)

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一
个引用类型值赋给该变量时,则这个值的引用次数就是 1。如果同一个值又
被赋给另一个变量,则该值的引用次数加 1。如下:

var obj={
  name: 'xiaoming',
  age:23
};//引用次数为1

var person =obj //引用次数为2


相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用
次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这个值
了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再
运行时,它就会释放那些引用次数为 0 的值所占用的内存。如下:

var obj={
  name: 'xiaoming',
  age:23
};//引用次数为1

var person =obj //引用次数为2
person = {} //引用次数减1,剩余引用次数为1
obj = {} //引用次数减1,剩余引用次数为0



Netscape Navigator3
是最早使用引用计数策略的浏览器,但很快它就遇
到一个严重的问题:循环引用。循环引用指的是对象 a 中包含一个指向对象
b
的指针,而对象 b 中也包含一个指向对象 a 的引用。

function fn(){
  var a={} //引用次数为1
  var b={} //引用次数为1
  
  a.pro=b;//引用次数为2
  b.pro=a; //引用次数为2
}

fn()



以上代码 a b 的引用次数都是 2fn()执行完毕后,两个对象都已经离开
环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为 a
b
的引用次数不为 0,所以不会被垃圾回收器回收内存,如果 fn 函数被大
量调用,就会造成内存泄露。在 IE7 IE8 上,内存直线上升。
IE
中有一部分对象并不是原生 js 对象。例如,其内存泄露 DOM BOM
中的对象就是使用 C++ COM 对象的形式实现的,而 COM 对象的垃圾
回收机制采用的就是引用计数策略。因此,即使 IE js 引擎采用标记清除
策略来实现,但 js 访问的 COM 对象依然是基于引用计数策略的。换句话
说,只要在 IE 中涉及 COM 对象,就会存在循环引用的问题。


var obj = {}
var elem = document.getElementById("box")

elem.someArr = obj
obj.someProperty = elem

如上例子中一个 dom 对象和一个原生 js 对象之间循环引用,即使 DOM
页面中移除,它也永远不会被回收。最简单的解决办法就是手动解除引用

elem.someArr = null
obj.someProperty = null

内存管理


计算机分配给 Web 浏览器的可用内存数量通常要比分配给桌面应用程序的
少。这样做的目的主要是出于安全方面的考虑,目的是防止运行 JavaScript
的网页耗尽全部系统内存而导致系统崩溃。因此,确保占用最少的内存可以
让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码
只保存必要的数据。一旦数据不再有用,最好通过将其值设置为 null 来释
放其引用——这个 做法叫做解除引用(dereferencing)。如下:

function personObj(name){
 var person = new Object();
 person.name = name;
 return person
}
var student = personObj("sunshine");
console.log(student.name)
//手动解除student的引用
student = null

这一做法适用于大多数全局变量和全局对象的属性。局部变量会在它们离开
执行环境时自动被解除引用

 

 

PHP断点
print_r();exit()


标签: javascript
分享给朋友:

相关文章

基于Vue自定义左右渐变图片轮播组件

基于Vue自定义左右渐变图片轮播组件

本文取自 https://github.com/zhangxiaoshang/va-carousel 的 一个基于 vue 的图片轮播组件 我在此基础上修改了一下,自己用...

Vue2.x基础知识

Vue2.x基础知识

1.vue绑定值与字符串拼接四种方法 A  :title="`字符串${变量}`"    B  :titl...

js基础之video视频、audio音频属性及API

js基础之video视频、audio音频属性及API

1、video 视频video支持三种格式 mp4 webm ogvvideo 里可放source标签,如果播放失败会继续检查下一个source标签,直到兼容不同浏览器差异safafi 只认mp4格式...

CSS grid 网格布局教程

CSS grid 网格布局教程

grid 兼容性查看请点此处 最新Grid兼容前言:用过这个grid只能说简直爽啊,以往需要float甚至position定位,自从学了grid网格布局,代码量非常精简但是也要注意兼容问题,...

前端日常编程代码便捷汇总

前端日常编程代码便捷汇总

1、使用form表单进行上传文件需要为form添加enctype="multipart/form-data" 属性,除此之外还需要将表单的提交方法改成post,如下 me...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

分享:

支付宝

微信