JS函数详解

 转自:https://blog.csdn.net/qq_34569497/article/details/95379260

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

目录

1.函数概念,声明及调用

2.函数表达式(匿名函数)

3.函数传参

4.修改input的值

5.函数的不定参(可变参)—关键字arguments

6.函数返回值

7.封装获取元素的方法

8.获取计算后样式—getComputedStyle(el)

8.JS预解析机制(变量提升Hoisting)

9.作用域

10.window

11.全局污染(命名冲突问题)

12.作用域链(scope chain)

13.闭包

14.this当前执行代码的环境对象

15.严格模式下的this指向

16.this指向的修改

16.1 function.call()

16.2 function.apply()

16.3 function.bind()

16.4 bind方法原理分析:


函数的基本概念、声明及调用;函数作用域、作用域链、闭包;this指向及修改和绑定this指向等。

1.函数概念,声明及调用

JS中的函数:把一段需要重复使用的代码,用function语法包起来,方便重复调用,分块和简化代码。复杂一点的,也会加入封装、抽象、分类等思想。

声明方式:严格意义上两种方式,但还有匿名函数

  • 方式一:
function 方法名(){
     //要执行的代码
}
  • 方式二:ES6中声明方式箭头函数,()=>{} 
  • 方式三:匿名函数,将函数存到变量里 var func = function(){};

函数调用:两种方式调用

  • 调用方式一:名字(); 函数可以多次调用
//函数声明
function fn(){
    console.log(1);
}
 
//函数的调用
fn();

调用方式二:在事件中调用,直接写函数名,不使用括号

//函数声明
function fn(){
    console.log(1);
}
 
//函数在事件中的调用
document.onclick = fn;

2.函数表达式(匿名函数)

函数表达式:就是把函数存到变量里。

匿名函数:没有名字的函数;

匿名函数在使用时只有两种情况:

  • 匿名函数自执行:声明后不需要调用就直接执行
(function(){
    console.log("匿名函数自执行");
})();
  • 函数表达式:把函数存到变量,或将函数存到数组的对应位置里等,调用时通过变量或数组对应位置进行调用。调用时需要写括号。
//2,函数表达式:把函数存到变量或数组等里,调用时通过变量进行调用
var fn = function(){
    console.log("函数表达式:将函数存到变量里");
};
fn();//调用时需要写括号
 
//2,函数表达式:把函数存到数组第0位,调用时通过数组第0位进行调用
var arr = [];
arr[0] = function(){
     console.log("函数表达式:将函数存到数组的对应位置");
};
arr[0]();//调用时需要写括号要写括号

结果:

事件函数扩展:给元素添加事件的说法是不正确的。事件时元素本身就具有的特征,只是触发事件后,默认没有相关的一些处理。这种操作其实就是给元素的某个事件添加一个事件处理函数。当事件被触发后,判断到属于该事件类型,就触发该事件函数的处理函数。

可以通过console.dir()把对象的所有属性和方法打印出来,查看对象或元素本身具有的事件。

 
  1. <script>

  2. //事件时元素本身就具有的特征,只不过,触发事件后,默认没有相关的一些处理。事件函数其实就是给元素的某个时间添加一个事件处理函数。

  3. //可以通过console.dir()把对象的所有属性和方法打印出来

  4. document.onclick = function(){

  5. console.log("事件的处理函数");

  6. };

  7. //当被触发后,判断到属于该事件类型,就触发该事件函数的处理函数

  8. if(typeof document.onclick == "function"){

  9. document.onclick();

  10. }

  11. </script>

 结果:

3.函数传参

获取元素,最好从父级元素获取,全部从document中获取,可能会出现混乱。

  • 形参:形式上的参数——给函数声明一个参数;
  • 实参:实际的参数——在函数调用时给形参赋的值
 
  1. function func(形参1,形参2){

  2. //函数执行代码

  3. }

  4.  
  5. func(实参1,实参2);//调用时传参

什么时候使用到传参?当有两段代码本身的功能极其相似,只有个别地方不一样时,就可以把两段代码合并成一个函数,然后把两段代码中不一致的内容通过传参传进去。

案例:自定义(多个模块)选项卡封装

 
  1. <!DOCTYPE html>

  2. <html lang="en">

  3. <head>

  4. <meta charset="UTF-8">

  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">

  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">

  7. <script src="../index.js"></script>

  8. <title>选项卡封装</title>

  9. <style>

  10. body { margin: 0; padding: 0; }

  11. .tab .active { background: red; }

  12. .cont div { display: none; }

  13. .cont .show { background: #000; font-size: 60px; color: white; width: 220px; height: 220px; display: block; }

  14. </style>

  15. </head>

  16. <body>

  17. <div id="wrap1" class="wrap">

  18. <div class="tab">

  19. <button class="active">选项卡一</button>

  20. <button>选项卡二</button>

  21. <button>选项卡三</button>

  22. </div>

  23. <div class="cont">

  24. <div class="show">内容一</div>

  25. <div>内容二</div>

  26. <div>内容三</div>

  27. </div>

  28. </div>

  29.  
  30. <div id="wrap2" class="wrap">

  31. <div class="tab">

  32. <button class="active">选项卡一</button>

  33. <button>选项卡二</button>

  34. <button>选项卡三</button>

  35. </div>

  36. <div class="cont">

  37. <div class="show">内容一</div>

  38. <div>内容二</div>

  39. <div>内容三</div>

  40. </div>

  41. </div>

  42. <div id="wrap3" class="wrap">

  43. <div class="tab">

  44. <button class="active">选项卡一</button>

  45. <button>选项卡二</button>

  46. <button>选项卡三</button>

  47. </div>

  48. <div class="cont">

  49. <div class="show">内容一</div>

  50. <div>内容二</div>

  51. <div>内容三</div>

  52. </div>

  53. </div>

  54. <script>

  55.  
  56. //通过id获取元素

  57. /*

  58. var wrap1 = _id("wrap1");

  59. var wrap2 = _id("wrap2");

  60. var wrap3 = _id("wrap3");

  61.  
  62. tab(wrap1);

  63. tab(wrap2);

  64. tab(wrap3);

  65. */

  66. //通过数组方式获取一组

  67. var wraps = _selectorAll(document,".wrap");//注意这里的parent父级已经是body了, 所以最好使用document

  68. for (var i = 0; i < wraps.length; i++) {

  69. tab(wraps[i]);

  70. }

  71. function tab(wrap){

  72. var btn = _selectorAll(wrap,".tab button");

  73. var divs = _selectorAll(wrap,".cont div");

  74.  
  75. var num = 0;//记录当前样式

  76. for (var i = 0; i < btn.length; i++) {

  77. btn[i].index = i;

  78. btn[i].onclick = function(){

  79. //清除当前样式

  80. btn[num].classList.remove("active");

  81. divs[num].classList.remove("show");

  82.  
  83. this.classList.add("active");

  84. divs[this.index].classList.add("show");

  85.  
  86. num = this.index;

  87. };

  88. }

  89. }

  90. </script>

  91. </body>

  92. </html>

index.js:

 
  1. function _id(idName){

  2. return document.getElementById(idName);

  3. }

  4. function _selector(parent,selector){

  5. return parent.querySelector(selector);

  6. }

  7. function _selectorAll(parent,selectors){

  8. return parent.querySelectorAll(selectors);

  9. }

 结果:

4.修改input的值

value和innerHTML都可以用来获取和修改元素的值(或内容);value只能获取特定的textarea和input的值,但是innerHTML可以获取所有HMTL元素的值。

不同之处如下:
1)value可以用来修改(获取)textarea和input的value属性的值或元素的内容;
2)innerHTML用来修改(获取)HTML元素(如div)html格式的内容。

 
  1. <!DOCTYPE html>

  2. <html lang="en">

  3. <head>

  4. <meta charset="UTF-8">

  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">

  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">

  7. <script src="../index.js"></script>

  8. <title>修改input的值</title>

  9. <style>

  10. .wrap input, .wrap button { display: none; }

  11. li { height: 30px; line-height: 30px; }

  12. </style>

  13. </head>

  14. <body>

  15. <div class="wrap">

  16. <ul>

  17. <li>

  18. <span id="first">这是第一个列表</span>

  19. <a href="javascript:;">修改</a>

  20. <input type="text">

  21. <button>确定</button>

  22. </li>

  23. <li>

  24. <span>这是第二个列表</span>

  25. <a href="javascript:;">修改</a>

  26. <input type="text">

  27. <button>确定</button>

  28. </li>

  29. <li>

  30. <span>这是第三个列表</span>

  31. <a href="javascript:;">修改</a>

  32. <input type="text">

  33. <button>确定</button>

  34. </li>

  35. <li>

  36. <span value="asf">这是第四个列表</span>

  37. <a href="javascript:;">修改</a>

  38. <input type="text">

  39. <button>确定</button>

  40. </li>

  41. </ul>

  42. </div>

  43. <script>

  44. var lis = _selectorAll(document,"li");

  45. for (var i = 0; i < lis.length; i++) {

  46. modify(lis[i]);

  47. }

  48. function modify(li){

  49. var modify = _selector(li,"a");

  50. var span = _selector(li,"span");

  51.  
  52. var input = _selector(li,"input");

  53. var btn = _selector(li,"button");

  54. //1.点击修改,“这是第一个列表”span标签隐藏,“修改”标签隐藏;input和确定按钮显示

  55. modify.onclick = function(){

  56. span.style.display = "none";

  57. this.style.display = "none";

  58. input.style.display = "inline-block";

  59. btn.style.display = "inline-block";

  60.  
  61. //注意:span标签只能通过.innerHTML获取元素的值,而不能通过.value

  62. //2.将span标签的文字内容赋值给input输入框

  63. input.value = span.innerHTML;

  64. };

  65. //3.点击确定按钮,先验证是否为空;再还原会span标签和修改a标签

  66. btn.onclick = function(){

  67. var txt = input.value;

  68. if(txt === ""){

  69. alert("请输入内容");

  70. }else{

  71. input.style.display = "none";

  72. this.style.display = "none";

  73. span.style.display = "inline-block";

  74. modify.style.display = "inline-block";

  75.  
  76. //4.将修改后的值赋给span标签

  77. span.innerHTML = txt;

  78. }

  79. };

  80. }

  81. </script>

  82. </body>

  83. </html>

结果:

 

5.函数的不定参(可变参)—关键字arguments

案例:购物车商品累计。事先不知道用户买多少商品

不定参(可变参)使用关键字:arguments,代表所有实参的集合。通过下标获取参数的每一位;通过length获取实参的个数;

集合是类数组,可以使用下标,但是没有数组中的各种方法。

 
  1. <!DOCTYPE html>

  2. <html lang="en">

  3. <head>

  4. <meta charset="UTF-8">

  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">

  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">

  7. <title>可变参(不定参):arguments</title>

  8. </head>

  9. <body>

  10. <script>

  11. //arguments 代表所有实参的集合(类数组),可以通过下标获取各个实参,通过length获取集合长度

  12. function args(){

  13. console.log(arguments);

  14. console.log("arguments的各个参数为:");

  15. for (var i = 0; i < arguments.length; i++) {

  16. console.log(arguments[i]);

  17. }

  18. }

  19.  
  20. args(23,45,999,10.90,"can","不定参");

  21. </script>

  22. </body>

  23. </html>

结果:

 

6.函数返回值

函数返回值即函数执行之后的返回结果。

  1. 所有函数都会有函数返回值即函数执行后一定会返回一个结果,如果没有定义默认返回undefined;
  2. 在函数中,return后定义返回值;
  3. 在函数中,return之后的代码就不会再执行了
  4. return只能用于函数中,用在其他地方会报错
 
  1. <script>

  2. function func1(){

  3. console.log("函数func1执行内容");

  4. }

  5. function func2(){

  6. console.log("函数func2执行内容");

  7. return 1;

  8. var a = 2;

  9. console.log(a);

  10. }

  11. //如果没有定义默认返回undefined

  12. console.log("func1返回结果:"+func1());//undefined

  13. console.log("func2返回结果"+func2());//1

  14. </script>

结果:发现return后的代码没有继续执行

7.封装获取元素的方法

封装通过id/CSS选择器获取(一般在父级下获取,所以传入父级和选择器名字)获取多个元素的方法,然后返回获取到的值

 
  1. //通过id名获取元素

  2. function _id(idName){

  3. return document.getElementById(idName);

  4. }

  5.  
  6. //通过CSS选择器获取一个元素

  7. function _selector(parent,selector){

  8. return parent.querySelector(selector);

  9. }

  10.  
  11. //通过CSS选择器获取一组元素

  12. function _selectorAll(parent,selectors){

  13. return parent.querySelectorAll(selectors);

  14. }

使用:

 
  1. var wrap1 = _id("wrap1");

  2. var wrap2 = _id("wrap2");

  3. var wrap3 = _id("wrap3");

  4.  
  5. var wraps = _selectorAll(document,".wrap");

  6.  
  7. var btn = _selectorAll(wraps,".tab button");

  8. var divs = _selectorAll(wraps,".cont div");

8.获取计算后样式—getComputedStyle(el)

点击时获取box的宽度,在原有基础上+100

  • 从style中获取的是行间样式,但是通常样式不会写在行间;
  • 获取计算后样式:getComputedStyle(el)获取元素的计算后样式。属于window的方法,即window.getComputedStyle(el)。在JS中使用window下的方法时,window可以不写;
  • getComputedStyle(el)方法不兼容IE6,7,8;
  • 计算后样式:优先级最高的样式,即当前显示出来的样式
  • 使用getComputedStyle(el)是获取window下所有的样式,getComputedStyle(el)['样式名']即可获取到特定样式
  • 写样式名时,使用驼峰样式的名字(否则IE下会有兼容问题),如margin-left必须写成marginLeft。
  • IE下获取元素的计算后样式,需要使用el.currentStyle['样式名']
  • 兼容IE和其他浏览器:判断el.currentStyle返回true即表示IE,否则就是其他浏览器,然后在对应浏览器下使用对应方法
  • 获取计算后样式会经常使用,因此可以封装成方法,进行复用
  • getComputedStyle(el)和el.currentStyle获取不到伪元素的样式,因为伪元素不是DOM的内容
  • 伪类样式计算后样式可以获取到,伪元素获取不到

标准浏览器下:transition:.5s;设置过渡效果的时间,否则div会直接变到从100+100的宽度

 
  1. <!DOCTYPE html>

  2. <html lang="en">

  3. <head>

  4. <meta charset="UTF-8">

  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">

  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">

  7. <script src="../index.js"></script>

  8. <title>getComputedStyle(el)</title>

  9. <style>

  10. #box { width: 100px; height: 100px; background: red; transition:.5s; }

  11. </style>

  12. </head>

  13. <body>

  14. <div id="box"></div>

  15. <script>

  16. //需求:点击div,div的width在原有基础上+100px

  17. var box = _id("box");

  18. var ss = 0;

  19. box.onclick = function(){

  20. //直接获取box.style.width是获取不到的,因为样式时写到style标签,而非行间样式

  21. //console.log(box.style.width);//获取不到值

  22.  
  23. //使用getComputedStyle(el)['样式名']即可获取到当前显示出来的样式

  24. var curStyle = getComputedStyle(box)['width'];

  25. //获取到的结果是“100px”,需要将其进行转为数字。width最后有可能会有小数点,所以最好使用parseFloat()。大多数情况下parseInt()亦可

  26. //但是parseInt(curStyle);才能去掉px进行相加

  27. //注意px前面的数字千万不要加引号

  28. this.style.width = parseInt(curStyle) + 100 + 'px';

  29. };

  30. </script>

  31. </body>

  32. </html>

结果:

不断点击后:

兼容问题:发现在IE6,7,8下并不支持getComputedStyle(el)方法

解决:通过el.currentStyle判断返回true代表在IE浏览器下,为false就不是在IE浏览器下。在IE浏览器下必须使用el.currentStyle才行

 
  1. //解决浏览器兼容问题:通过el.currentStyle判断返回true代表在IE浏览器下,为false就不是在IE浏览器下。在IE浏览器下必须使用el.currentStyle才行

  2. function currStyle(el,styleName){

  3. // var curStyle = '';

  4. // if(el.currentStyle){

  5. // curStyle = el.currentStyle[styleName];

  6. // }else{

  7. // curStyle = getComputedStyle(el)[styleName];

  8. // }

  9. // return curStyle;

  10. //使用三元获取返回值

  11. return el.currentStyle?el.currentStyle[styleName]:getComputedStyle(el)[styleName];

  12. }

 
  1. <!DOCTYPE html>

  2. <html lang="en">

  3. <head>

  4. <meta charset="UTF-8">

  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">

  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">

  7. <script src="../index.js"></script>

  8. <title>getComputedStyle(el)</title>

  9. <style>

  10. #box { width: 100px; height: 100px; background: red; transition:.5s; }

  11. </style>

  12. </head>

  13. <body>

  14. <div id="box"></div>

  15. <script>

  16. //需求:点击div,div的width在原有基础上+100px

  17. var box = _id("box");

  18.  
  19. box.onclick = function(){

  20. //直接获取box.style.width是获取不到的,因为样式时写到style标签,而非行间样式

  21. //console.log(box.style.width);//获取不到值

  22.  
  23. //使用getComputedStyle(el)['样式名']即可获取到当前显示出来的样式

  24. // var curStyle = getComputedStyle(box)['width'];

  25. var curStyle = currStyle(box,'width');

  26. //获取到的结果是“100px”,需要将其进行转为数字。width最后有可能会有小数点,所以最好使用parseFloat()。大多数情况下parseInt()亦可

  27. //但是parseInt(curStyle);才能去掉px进行相加

  28. //注意px前面的数字千万不要加引号

  29. this.style.width = parseInt(curStyle) + 100 + 'px';

  30. };

  31. </script>

  32. </body>

  33. </html>

结果:

8.JS预解析机制(变量提升Hoisting)

JS预解析机制(变量提升(Hoisting)):JS在读取到一个script标签(或者一个函数作用域)时,会先进行一个预解析的过程,在这个过程中,会把var声明的变量和function声明的函数体,提升到整个scriptt标签(或者一个函数作用域)最前边去。在预解析完之后,JS才会从上到下一行一行解析代码并执行。

  • var在预解析时,会把声明提升到最前边(赋值前打印返回undefined)。只提升声明,不会把赋值过程进行提升。
  • function的函数体在预解析时,会把整个函数体提升至最前边。(函数体:function fn(){ console.log(1);})
  • 函数表达式(函数表达式:var fn = function(){};)只会提升函数表达式的声明,不会执行(真正执行函数表达式前调用会返回undefined)
  • 在预解析时,会先预解析var(包括变量声明和函数表达式的变量声明),把var放在最前面,然后再预解析function,所以当var和function重名时,function会覆盖var;
 
  1. //JS var变量的预解析

  2. console.log("var变量的预解析:"+a);//undefined

  3. var a = 0;

  4.  
  5. //JS函数体的预解析

  6. console.log("函数体的预解析:"+fn);

  7. function fn(){

  8. console.log("函数");

  9. }

  10. //JS函数表达式的预解析

  11. console.log("函数表达式的预解析:"+fnn);

  12. var fnn = function(){

  13. console.log("函数表达式");

  14. };

结果:

在预解析时,会先预解析var,把var放在最前面,然后再预解析function,所以当var和function重名时,function会覆盖var:

 
  1. /*

  2. 解析过程:先预解析var声明的a,再预解析函数体a,后面覆盖前面,所以最后结果是function函数体

  3. var a;

  4. function a(){console.log("函数a");};//此函数体解析后会覆盖变量a

  5. */

  6. console.log(a);

  7. var a = 0;

  8. function a(){

  9. console.log("函数a");

  10. }

结果:

JS预解析示例:

 
  1. //JS预解析过程:遇到Script标签或一个函数作用域,会先进行预解析,先预解析var声明的变量(包括普通变量声明和函数表达式声明),再声明function函数体,如果有重名function声明会覆盖var声明

  2. /*

  3. 预解析之后代码:

  4. var a;//变量a

  5. var a;//函数表达式a(函数表达式也是var声明)

  6. function a(){//函数体

  7. console.log(1);

  8. };

  9.  
  10. console.log(a);//打印函数体a

  11. var a = 10;

  12. console.log(a);//10

  13. console.log(a);//10

  14. a = function(){

  15. console.log(2);//打印函数表达式a

  16. };

  17. console.log(a);

  18.  
  19. */

  20. console.log(a);

  21. var a = 10;

  22.  
  23. console.log(a);

  24. function a(){

  25. console.log(1);

  26. };

  27.  
  28. console.log(a);

  29. var a = function(){

  30. console.log(2);

  31. };

  32.  
  33. console.log(a);

结果:

JS预解析机制不是良好的编码习惯,不利于代码维护,建议不要使用,编码时建议先声明,再使用。

扩展:从概念的字面意义上说,“变量提升”意味着变量和函数的声明会在物理层面移动到代码的最前面,但这么说并不准确。实际上变量和函数声明在代码里的位置是不会动的,而是在编译阶段被放入内存中。

ES6之后就不能像JS预解析这么编写JS代码了。

9.作用域

通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。

通俗的说,作用域:数据起作用的范围(某条数据可以在什么范围内使用)

前端权威官方MDN:https://developer.mozilla.org/en-US/docs/Glossary/Scope

作用域的分类:

  1. - 全局作用域:通过var或function声明在全局(声明在任意函数之外和代码块之外)中的数据,在全局的任意地方都可以调用或修改(即全局变量)和在window下的属性
  2. - 局部作用域:
  • 函数作用域:声明在函数内部的某个数据(var,function,参数),就只能在函数内部使用(函数的局部作用域)
  • 块级作用域(ES6新增)

全局作用域:

 
  1. //声明在全局中的变量

  2. var a = 0;

  3. console.log(a);//可在全局任意地方调用

  4. function fn(){

  5. console.log(a);//可在函数中调用

  6. a = 10;//可在任意地方修改全局中的变量

  7. }

  8. fn();

  9. console.log(a);

结果:

10.window

  • 在JS中,默认情况下 var声明的全局变量和function声明的全局函数会挂载在window上(所以要避免声明全局变量和全局函数)
  • 在JS中,默认全局数据都会保存在window下(ES6之前)
  • 另外window是JS在浏览器里的顶层对象,所以window的属性和方法也都是全局的
  • 在JS中,调用window下的属性和方法,默认可以不写window,所以如果在函数里面声明变量没有写var,会把其当做window的一个属性;(不规范写法,要避免)
 
  1. //var声明的全局变量和function声明的全局函数,都默认挂载在window上

  2. var a = 0;

  3. console.log(window);

  4.  
  5. function fn(){

  6. b = 10;

  7. console.log(1);

  8. }

  9. fn();

  10. console.log(b);//此处b没有写声明的var所以,b=10即为window.b = 10;相当于挂载在window上的全局变量

  11.  
  12. //在JS中,默认全局数据都会保存在window下

  13. //window是浏览器的最顶层对象,所以默认window下的属性和方法都是全局的。所以window下的方法和属性,默认可以不写window

 结果:全局变量a和全局函数fn()都默认挂载在window上。而且,此处b没有写声明的var,所以,b=10即相当于window.b = 10;也是挂载在window上的全局变量:

11.全局污染(命名冲突问题)

全局变量污染:大家都在全局中写代码,很容易造成命名冲突,导致代码冲突。ES6中代码冲突会直接报错。所以要养成好的习惯不要在全局去声明变量。

 
  1. <!DOCTYPE html>

  2. <html lang="en">

  3. <head>

  4. <meta charset="UTF-8">

  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">

  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">

  7. <title>全局污染</title>

  8. </head>

  9. <body>

  10. <div id="list"></div>

  11. <div class="memulist"></div>

  12. <script>

  13. var list = document.getElementById("list");

  14. var list = document.querySelector(".memulist");

  15. console.log(list);

  16. </script>

  17. </body>

  18. </html>

 结果:发现最后获取的只有一个元素,所以很容易造成代码冲突

解决:不要声明全局变量

 
  1. (function(){

  2. var list = document.getElementById("list");

  3. console.log(list);

  4. })();

  5.  
  6. (function(){

  7. var list = document.querySelector(".memulist");

  8. console.log(list);

  9. })();

结果: 

JS中提供了id使用的简便方法,直接调用id名即可:但尽量不要这么写,不规范:

console.log(list);

匿名函数:匿名函数自执行本身就是为了避免在全局写代码,避免冲突的。匿名函数自执行也叫开启一个新的命名空间。即开启新的作用域,此作用域和其他的不会冲突。

12.作用域链(scope chain)

作用域链决定了哪些数据能被函数访问。当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。

作用域链:JS中数据的查找规则。

作用域链查找过程:在JS中我们调用一条数据时,会先在当前作用域进行查找,如果找不到,就从向上找父作用域的数据,还找不到就接着向上,一直找到全局作用域(window对象),window都找不到就报错。

 
  1. //调用fn()时,在其子函数fn2()被调用时,首先会在fn2()自己的作用域内找变量a

  2. //找不到就在其父级作用域即fn()作用域中找,即a=10,然后打印a=10

  3. function fn(){

  4. var a = 10;

  5. function fn2(){

  6. console.log(a);

  7. }

  8. fn2();

  9. }

  10. fn();

 结果:

作用域链查找关系图:

 作用域链示例:

 
  1. <script>

  2. function fn(){

  3. var b = 0;

  4. return function(){

  5. b++;

  6. console.log(b);

  7. };

  8. }

  9. var f = fn();

  10. console.log(f);//ƒ (){ b++; console.log(b); }

  11. f();//1

  12. f();//2

  13. f();//3

  14. fn()();//1

  15. </script>

结果:

解析(函数拆分)三个f():

注意:这里的var f = fn();是将函数fn()的返回值函数体f(){ b++; console.log(b); };赋给变量f,但是并没有执行该返回值函数体,当f()调用时,便执行了该函数体。f此时是fn的子函数,那它可以访问和更改父级fn的作用域中的b。

b变量会一直赋值,是因为JS的垃圾回收机制决定的,只要检测都有引用存在,就不会释放。

 
  1. <!DOCTYPE html>

  2. <html lang="en">

  3. <head>

  4. <meta charset="UTF-8">

  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">

  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">

  7. <title>作用域链示例-函数拆分</title>

  8. </head>

  9. <body>

  10. <script>

  11. // function fn(){

  12. // var b = 0;

  13. // return function(){

  14. // b++;

  15. // console.log(b);

  16. // };

  17. // }

  18. // var f = fn();

  19. // f();//1

  20. // f();//2

  21. // f();//3

  22. // fn()();//1

  23. //这里的var f = fn();是将函数fn()的返回值函数体function(){ b++; console.log(b); };赋给变量f,但是并没有执行该返回值函数体,当f()调用时,便执行了该函数体

  24. //即这里的三个f()相当于,在函数fn()中写了三个子函数,再进行调用

  25. //由于b变量对于fn1(),fn2(),fn3()都是父级变量,所以每次b++都会将b的值+1,所以最后得到的值即1,2,3

  26. function fn(){

  27. var b = 0;

  28. function fn1(){

  29. b++;

  30. console.log(b);

  31. }

  32. fn1();

  33. function fn2(){

  34. b++;

  35. console.log(b);

  36. }

  37. fn2();

  38. function fn3(){

  39. b++;

  40. console.log(b);

  41. }

  42. fn3();

  43. }

  44.  
  45. fn();

  46. </script>

  47. </body>

  48. </html>

结果:所以执行三个f();和执行三个fn();得到的结果是不一样的。

以上进一步分解:

 

 
  1. <script>

  2. // function fn(){

  3. // var b = 0;

  4. // return function(){

  5. // b++;

  6. // console.log(b);

  7. // };

  8. // }

  9. // var f = fn();

  10. // f();//1

  11. // f();//2

  12. // f();//3

  13. // fn()();//1

  14. //这里的var f = fn();是将函数fn()的返回值函数体function(){ b++; console.log(b); };赋给变量f,但是并没有执行该返回值函数体,当f()调用时,便执行了该函数体

  15. //即这里的三个f()相当于,在函数fn()中写了三个子函数,再进行调用

  16. //由于b变量对于fn1(),fn2(),fn3()都是父级变量,所以每次b++都会将b的值+1,所以最后得到的值即1,2,3

  17. function fn(){

  18. var b = 0;

  19. // function fn1(){

  20. // b++;

  21. // console.log(b);

  22. // }

  23. // fn1();

  24. // function fn2(){

  25. // b++;

  26. // console.log(b);

  27. // }

  28. // fn2();

  29. // function fn3(){

  30. // b++;

  31. // console.log(b);

  32. // }

  33. // fn3();

  34. function fnn(){

  35. b++;

  36. console.log(b);

  37. }

  38. fnn();

  39. fnn();

  40. fnn();

  41.  
  42. }

  43. fn();

  44. </script>

结果:所以调用三次f()和再fn()函数里执行三次fnn()是一样的

函数拆分fn()():

 
  1. <!DOCTYPE html>

  2. <html lang="en">

  3. <head>

  4. <meta charset="UTF-8">

  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">

  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">

  7. <title>作用域链示例——函数拆分fn()()</title>

  8. </head>

  9. <body>

  10. <script>

  11. // function fn(){

  12. // var b = 0;

  13. // return function(){

  14. // b++;

  15. // console.log(b);

  16. // };

  17. // }

  18. // var f = fn();

  19. // f();//1

  20. // f();//2

  21. // f();//3

  22. // fn()();//1

  23.  
  24. //此处的fn()();第一个括号表示执行fn()函数,第二个括号表示执行fn()函数中返回值中的函数,所以fn()()相当于整个fn函数再执行一次

  25. //函数每次调用,都相当于把这个代码复制出来执行了一遍。所以fn()();每次执行都是重新执行一遍代码,相当于以下:

  26. function fn(){

  27. var b = 0;

  28. return function(){

  29. b++;

  30. console.log(b);

  31. };

  32. }

  33. function fnA(){

  34. var b = 0;

  35. return function(){

  36. b++;

  37. console.log(b);

  38. };

  39. }

  40. fn()();//1

  41. fnA()();//1

  42. </script>

  43. </body>

  44. </html>

结果:

函数每次调用,如fn()和fnA()之间没有任何关联,都相当于把这个代码复制出来执行了一遍。

13.闭包

闭包是对作用域链的一种表现和使用。

函数对象可以通过作用域链相互关联起来,函数体内的数据(变量和函数声明)都可以保存在函数作用域内,这种特性在计算机科学文献中被称为“闭包”。既函数体内的数据被隐藏于作用于链内,看起来像是函数将数据“包裹”了起来。从技术角度来说,js的函数都是闭包:函数都是对象,都关联到作用域链,函数内数据都被保存在函数作用域内。

fn()();调用函数后的返回值还是一个函数,也对其进行执行

函数的每次执行之间没有任何关系。每次执行都相当于在JS内部吧代码重新写了一遍。

面试时:闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

闭包:

  • 形式:函数嵌套函数;
  • 作用:子函数可以访问父函数的作用域,但是父级不能访问子级的。

闭包示例:

 
  1. <!DOCTYPE html>

  2. <html lang="en">

  3. <head>

  4. <meta charset="UTF-8">

  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">

  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">

  7. <title>闭包</title>

  8. </head>

  9. <body>

  10. <script>

  11. function fn(){

  12. var a = 0;

  13. function fn2(){

  14. console.log(a);//fn2是fn的子函数,所以可以访问父级作用域的a

  15. }

  16. fn2();

  17. }

  18. fn();

  19. console.log(a);//在fn函数作用域外,不能访问其子级作用域fn中的变量a

  20. </script>

  21. </body>

  22. </html>

结果:

闭包应用:i传参给了fn函数,而点击事件是fn函数的子函数,所以也可以获取到fn函数中的i

 
  1. <!DOCTYPE html>

  2. <html lang="en">

  3. <head>

  4. <meta charset="UTF-8">

  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">

  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">

  7. <title>闭包应用</title>

  8. </head>

  9. <body>

  10. <button>按钮一</button>

  11. <button>按钮二</button>

  12. <button>按钮三</button>

  13. <script>

  14. var btns = document.querySelectorAll("button");

  15. for (var i = 0; i < btns.length; i++) {

  16. fn(i);

  17. }

  18. //i传参给了fn函数,而点击事件是fn函数的子函数,所以也可以获取到fn函数中的i

  19. function fn(index){

  20. btns[index].onclick = function(){

  21. console.log(index);

  22. };

  23. }

  24. </script>

  25. </body>

  26. </html>

结果:按钮进行循环后会将当前index传参给fn函数,当点击按钮时,再通过父级作用域获取到父级的index

闭包应用二:(匿名函数自执行方式)页面刷新时解析for循环并将index传给fn,并且立即执行fn函数,当点击按钮时,再通过父级作用域fn获取到index

 
  1. <script>

  2. var btns = document.querySelectorAll("button");

  3. for (var i = 0; i < btns.length; i++) {

  4. (function fn(index){

  5. btns[index].onclick = function(){

  6. console.log(index);

  7. };

  8. })(i);

  9. }

  10. //i传参给了fn函数,而点击事件是fn函数的子函数,所以也可以获取到fn函数中的i

  11. // function fn(index){

  12. // btns[index].onclick = function(){

  13. // console.log(index);

  14. // };

  15. // }

  16. </script>

结果:

闭包应用三:点击按钮后,再立即执行一个匿名函数自执行。匿名函数自执行后,得到的是一个函数返回值,当点击时再执行该函数中的内容

 
  1. <script>

  2. var btns = document.querySelectorAll("button");

  3. for (var i = 0; i < btns.length; i++) {

  4. //匿名函数自执行后,得到的是一个函数返回值,当点击时再执行该函数中的内容

  5. btns[i].onclick = (function fn(index){

  6. return function(){

  7. console.log(index);

  8. };

  9. })(i);

  10. }

  11. </script>

结果:

14.this当前执行代码的环境对象

默认情况下:

  • 函数外:window
  • 函数内:函数中的this指向谁,取决于这个函数是怎么调用的
  • 严格模式下,默认为undefined
  1. 作为对象的属性(方法,事件(方法的一种))调用,指向当前对象
  2. 其余情况执行window
 
  1. <script>

  2. //函数外:window

  3. // 函数内:函数中的this指向谁,取决于这个函数是怎么调用的

  4. // 作为对象的属性(方法)调用,指向当前对象

  5. // 其余情况执行window

  6.  
  7. function fn(){

  8. console.log(this);

  9. }

  10. //直接调用函数,this代表window

  11. console.log("没有作为对象的属性进行调用,而是直接调用:");

  12. fn();//this指向window

  13.  
  14. //作为对象的属性或方法调用

  15. //作为对象的属性进行调用

  16. console.log("作为对象的属性进行调用:");

  17. document.fn = fn;

  18. document.fn();//this 执行document

  19.  
  20. //事件里,把this绑定在事件上

  21. console.log("作为对象的属性(事件)进行调用:");

  22. document.onclick = fn;

  23. document.onclick();//this 执行document

  24.  
  25. //数组里,把函数放到数组里,再由数组调用,此时this指向当前数组

  26. console.log("作为对象(数组)进行调用:");

  27. var arr = [fn,1,2];

  28. arr[0]();//this指向当前数组

  29.  
  30. //obj对象里

  31. console.log("作为对象(object对象)进行调用:");

  32. var obj = {

  33. fn:fn

  34. };

  35. obj.fn();//this指向object对象

  36. </script>

结果:

15.严格模式下的this指向

 在script标签最上面加上 'use strict';,加上'use strict'后预解析已经不能使用,会报错。

严格模式下的function指向问题:在严格模式下,function如果不是作为对象的属性和方法被调用(即直接调用方法)就指向undefined。

 
  1. <script>

  2. 'use strict';

  3. function fn(){

  4. console.log(this);

  5. };

  6. console.log("严格模式下,函数直接被调用(没有通过函数的属性或方法被调用,this就指向undefined):");

  7. //严格模式下,函数直接被调用(没有通过函数的属性或方法被调用,this就指向undefined)

  8. fn();

  9.  
  10. //通过函数的属性或方法被调用,就指向被调用的对象

  11. console.log("通过函数的属性或方法被调用,就指向被调用的对象:");

  12. document.onclick = fn;

  13. document.onclick();

  14.  
  15. </script>

结果:

16.this指向的修改

16.1 function.call()

  1. function.call(this指向谁,参数1,参数2...)调用函数,并修改函数中的this指向;
  2. 执行函数的call方法,会调用该函数,并且修改函数中 的this指向;
  3. call中的第0个参数,代表当前函数执行时,函数中的this指向谁
  4. 其他参数都是给函数传的实参
  5. 注意修改执行为body时,一定要使用document.body
 
  1. <script>

  2. function fn(a,b){

  3. console.log(this,a,b);

  4. }

  5. //直接执行,this指向window

  6. console.log("直接调用函数,this指向window:");

  7. fn(1,2);//window

  8.  
  9. //通过call更改当前函数的this指向

  10. //更改this指向为document

  11. console.log("调用函数的call方法,更改this指向document:");

  12. fn.call(document,'a','b');//document

  13.  
  14. //更改this指向为document.body

  15. console.log("调用函数的call方法,更改this指向document.body:");

  16. fn.call(document.body,'a','b');//body

  17.  
  18. </script>

结果:

16.2 function.apply()

  1. function.apply(this指向谁,[参数1,参数2...])调用函数,并修改函数中的this指向
  2. 指向函数的apply方法,会调用该函数,并且修改函数中的this指向;
  3. apply中的第0个参数,代表当前执行时,函数中的this指向谁;
  4. apply中第1个参数是个数组,数组中代表了我们要往函数中传递的参数;且所有参数只能放在一个数组里,有多个数组时,除了第一个,其他数值的参数不会被接收
  5. apply和call唯一的区别在于,call方法直接在方法里传参,而apply是将所有参数已数组形式进行传递;
  6. 注意修改执行为body时,一定要使用document.body
 
  1. <script>

  2. function fn(a,b){

  3. console.log(this,a,b);

  4. }

  5. //直接调用,this指向window

  6. console.log("直接调用,this指向window:");

  7. fn('s','r');

  8.  
  9. //调用函数的apply方法,更改this指向为document

  10. console.log("调用函数的apply方法,更改this指向document:");

  11. fn.apply(document,['2','4']);

  12.  
  13. //调用函数的apply方法,更改this指向document.body

  14. console.log("调用函数的apply方法,更改this指向document.body:");

  15. fn.apply(document.body,['2','4']);

  16. </script>

结果: 

16.3 function.bind()

  1. function.bind(指向,参数1,参数2,...)绑定this指向
  2. 调用函数的bind方法,会返回一个绑定了this执行的新函数;
  3. 第0个参数是bind返回的新函数的this指向
  4. 返回新函数的this指向被绑定了,不能再被更改
  5. 新函数的this指向在修改原函数this指向时就已经被绑定,一旦被绑定不能再次修改

总结:调用函数的bind方法,会生成新的函数,绑定的this指向是针对新函数的,新函数this指向被绑定后,不能再继续被绑定(call和apply也不行);如果调用时再传入新的参数,会将新的参数和被绑定的参数进行合并,被绑定的参数会一直存在;而原函数的this指向一直没有变,还可以继续调用bind方法,生成新的函数,同时给新的函数绑定新的this指向

 
  1. <script>

  2. function fn(a,b){

  3. console.log(this,arguments);

  4. }

  5. //直接调用函数,this指向window

  6. console.log("直接调用函数,this指向window:");

  7. fn(1,2);//window

  8.  
  9. //使用函数的bind方法

  10. console.log("使用函数的bind方法,返回新的函数:");

  11. var fn2 = fn.bind(document,3,4);

  12. console.log(fn2 == fn);//false 新函数和旧函数不是同一个

  13.  
  14. console.log("原函数的this指向:");

  15. fn(5,6);//原函数的this指向不变,依然是window,且还可以继续调用bind方法

  16.  
  17. console.log("新的函数的this指向:");

  18. console.log("如果新的函数调用时传入新的参数,会将绑定的参数和新传入的参数进行合并:");

  19. fn2(7,8);//3,4,7,8 新函数的this指向即原函数绑定的this指向

  20.  
  21. //新函数的this指向在修改原函数this指向时就已经被绑定,一旦被绑定不能再次修改,且被绑定的参数也不能再被修改

  22. //只是如果调用新函数时传入新参数,会合并两次的参数

  23. console.log("新函数的this指向再修改原函数this指向时就已经被绑定,一旦被绑定不能再次修改:");

  24. fn2.call(window,9,0);//这里即使再次更改this指向,fn2新函数的this指向永远不会再改变

  25.  
  26. //再次调用fn的bind方法

  27. console.log("再次调用fn的bind方法,返回新的函数:");

  28. var fn3 = fn.bind(document.body,'a','b');

  29. console.log(fn3 == fn);

  30. fn3('c','d');

  31. </script>

结果:

16.4 bind方法原理分析:

为什么调用bind生成的新函数,this指向被绑定后就不能再绑定了?bind方法在ES5.1后才出来,如果想实现此功能,可以自己写可以再次绑定的bind方法。

自己实现bind方法:

参数:fn 要绑定this函数;_this返回新函数this指向谁

 
  1. <script>

  2. //为什么调用bind生成的新函数,this指向被绑定后就不能再绑定了

  3. //自己实现bind方法的原理

  4. //fn要绑定的新函数,_this给新函数绑定的this指向,...arg传参(展开运算符ES6新增)

  5. function bind(fn,_this,...arg){

  6. return function(...args2){

  7. fn.call(_this,...arg,...args2);

  8. };

  9. }

  10.  
  11. function fn(){

  12. console.log(this,arguments);

  13. }

  14. //调用bind方法,返回新的函数function(){ fn.call(_this); };

  15. var fn2 = bind(fn,document,11);

  16. //执行fn2()即执行返回的新函数,并给新的函数绑定this指向

  17. fn2(2,3);//document

  18. fn();

  19.  
  20. //重新调用绑定函数,并返回新的函数同时给其绑定this执行

  21. var fn3 = bind(fn,document.body,12);

  22. fn3(4,5);//this

  23. </script>

结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值