JS知识总结

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

  1. JS中的函数:把一段需要重复使用的代码,用function语法包起来,方便重复调用,分块和简化代码。复杂一点的,也会加入封装、抽象、分类等思想。
  2. 声明: 三种方式
  • 方式一 :普通函数
function fn()
{
console.log('我是一个普通函数');
}

//调用
fn(); // 我是一个普通函数
  • 方式二:匿名函数
    函数表达式
var func = function(){
console.log('我是一个匿名函数');

func ();//我是一个匿名函数
};
  • 方式三:箭头函数
()=>{
console.log('我是一个箭头函数');
}

2.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;
 //JS var变量的预解析
    console.log("var变量的预解析:"+a);//undefined
    var a = 0;
    
           //此时正确的执行顺序为
   			    var a;
 			   console.log("var变量的预解析:"+a);
 			   a = 0;
 		
    //JS函数体的预解析
    console.log("函数体的预解析:"+fn()); //函数体的预解析: 我被调用了
    function fn(){
        console.log("函数");
    }
   //此时正确的执行顺序为
 			  function fn(){
      			  console.log("函数");
      			  return '我被调用了';
    			}
    		 console.log("函数体的预解析:"+fn);
     
    //JS函数表达式的预解析
    console.log("函数表达式的预解析:"+fnn);//函数表达式的预解析:undefined
    var fnn = function(){
        console.log("函数表达式");
        return '我被调用了';
    };
     //此时正确的执行顺序为
    		 var fnn;
   			  console.log("函数表达式的预解析:"+fnn());
   				  fnn = function(){
		        console.log("函数表达式");
    			};


3.作用域

全局作用域:

  1. 声明在任何函数以外的变量或者函数体 - 全局变量(全局函数)
  2. 在浏览器中,默认不写var 就是全局变量

局部作用域 :

  1. 声明在函数内部的数据,作用域只在函数内部

块级作用域


4.作用域链

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

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


<script>
    function fn(){
        var b = 0;
        return function(){
            b++;
            console.log(b);
        };
    }
    var f = fn();
    console.log(f);//ƒ (){ b++; console.log(b); }
    f();//1
    f();//2
    f();//3
    fn()();//1

</script>
//对上面函数的理解
//f 此时等于fn 的返回值
var f = fn();
此时 f = {
            b++;
            console.log(b);
        };
      f();//就是执行return 中的函数
      //可以看作fn()();

立即执行函数:声明一个函数,并马上调用这个匿名函数就叫做立即执行函数;即立即执行函数是定义函数以后立即执行该函数

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

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

<body>
    <div id="list"></div>
    <div class="memulist"></div>
    <script>
        var list = document.getElementById("list");
        var list = document.querySelector(".memulist");
        
        //命名冲突导致只获取了最后一个dom
        //<div class="memulist"></div>
    	console.log(list); 
        
    </script>
    </body>
  • 结果:发现最后获取的只有一个元素,所以很容易造成代码冲突

  • 解决:不要声明全局变量

1.立即执行函数:声明一个函数,并马上调用这个匿名函数就叫做立即执行函数;即立即执行函数是定义函数以后立即执行该函数

(function(){
     var list = document.getElementById("list");
     console.log(list);
})();
 
(function(){
      var list = document.querySelector(".memulist");
      console.log(list);
})();      //结果
      

6.闭包

1.概念
闭包函数:声明在一个函数中的函数,叫做闭包函数。

闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。

2、特点
让外部访问函数内部变量成为可能;

局部变量会常驻在内存中;

可以避免使用全局变量,防止全局变量污染;

会造成内存泄漏(有一块内存空间被长期占用,而不被释放)

3、闭包的创建:­­­
闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,互不干扰。闭包会发生内存泄漏,每次外部函数执行的时 候,外部函数的引用地址不同,都会重新创建一个新的地址。但凡是当前活动对象中有被内部子集引用的数据,那么这个时候,这个数据不删除,保留一根指针给内部活动对象。

闭包内存泄漏为: key = value,key 被删除了 value 常驻内存中; 局部变量闭包升级版(中间引用的变量) => 自由变量;

4、闭包的应用场景
结论:闭包找到的是同一地址中父级函数中对应变量最终的值
最终秘诀就这一句话,每个例子请自行带入这个结论!!!!!!!!!!!!!

  • 例子1
function outerFn(){
  var i = 0; 
  function innerFn(){
      i++;
      console.log(i);
  }
  return innerFn;
}
var inner = outerFn();  //每次外部函数执行的时候,都会开辟一块内存空间,外部函数的地址不同,都会重新创建一个新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2();   //1 2 3 1 2 3
  • 例子2
function outerFn(){
var i = 0;
  function innnerFn(){
      i++;
      console.log(i);
  }
  return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2();    //1 1 2 2

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

默认情况下

  • 函数外:window
  • 函数内:函数中的this指向谁,取决于这个函数是怎么调用的
    严格模式下,默认为undefined
    作为对象的属性(方法,事件(方法的一种))调用,指向当前对象
    其余情况执行window
<script>
//函数外:window
// 函数内:函数中的this指向谁,取决于这个函数是怎么调用的
    // 作为对象的属性(方法)调用,指向当前对象
    // 其余情况执行window
 
function fn(){
    console.log(this);
}
//直接调用函数,this代表window
console.log("没有作为对象的属性进行调用,而是直接调用:");
fn();//this指向window 
 
//作为对象的属性或方法调用
//作为对象的属性进行调用
console.log("作为对象的属性进行调用:");
document.fn = fn;
document.fn();//this 执行document
 
//事件里,把this绑定在事件上
console.log("作为对象的属性(事件)进行调用:");
document.onclick = fn;
document.onclick();//this 执行document
 
//数组里,把函数放到数组里,再由数组调用,此时this指向当前数组
console.log("作为对象(数组)进行调用:");
var arr = [fn,1,2];
arr[0]();//this指向当前数组
 
//obj对象里
console.log("作为对象(object对象)进行调用:");
var obj = {
    fn:fn
};
obj.fn();//this指向object对象
</script>

8.严格模式下的this指向

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

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

<script>
'use strict';
function fn(){
    console.log(this);
};
console.log("严格模式下,函数直接被调用(没有通过函数的属性或方法被调用,this就指向undefined):");
//严格模式下,函数直接被调用(没有通过函数的属性或方法被调用,this就指向undefined)
//undefined
fn();
 
//通过函数的属性或方法被调用,就指向被调用的对象
console.log("通过函数的属性或方法被调用,就指向被调用的对象:");
document.onclick = fn;

//document
document.onclick();
 
</script>

9 .this指向的修改

  • 1 function.call()
    function.call(this指向谁,参数1,参数2…)调用函数,并修改函数中的this指向;
    执行函数的call方法,会调用该函数,并且修改函数中 的this指向;
    call中的第0个参数,代表当前函数执行时,函数中的this指向谁
    其他参数都是给函数传的实参
    注意修改执行为body时,一定要使用document.body
<script>
function fn(a,b){
    console.log(this,a,b);
}
//直接执行,this指向window
console.log("直接调用函数,this指向window:");
fn(1,2);//window
 
//通过call更改当前函数的this指向
//更改this指向为document
console.log("调用函数的call方法,更改this指向document:");
fn.call(document,'a','b');//document 
 
//更改this指向为document.body
console.log("调用函数的call方法,更改this指向document.body:");
fn.call(document.body,'a','b');//body

</script>  
 

结果:

  • 16.2 function.apply()
    function.apply(this指向谁,[参数1,参数2…])调用函数,并修改函数中的this指向
    指向函数的apply方法,会调用该函数,并且修改函数中的this指向;
    apply中的第0个参数,代表当前执行时,函数中的this指向谁;
    apply中第1个参数是个数组,数组中代表了我们要往函数中传递的参数;且所有参数只能放在一个数组里,有多个数组时,除了第一个,其他数值的参数不会被接收
    apply和call唯一的区别在于,call方法直接在方法里传参,而apply是将所有参数已数组形式进行传递;
    注意修改执行为body时,一定要使用document.body
<script>
function fn(a,b){
    console.log(this,a,b);
}
//直接调用,this指向window
console.log("直接调用,this指向window:");
fn('s','r');
 
//调用函数的apply方法,更改this指向为document
console.log("调用函数的apply方法,更改this指向document:");
fn.apply(document,['2','4']);
 
//调用函数的apply方法,更改this指向document.body
console.log("调用函数的apply方法,更改this指向document.body:");
fn.apply(document.body,['2','4']);
</script> 
  • 3 function.bind()
    function.bind(指向,参数1,参数2,…)绑定this指向
    调用函数的bind方法,会返回一个绑定了this执行的新函数;
    第0个参数是bind返回的新函数的this指向
    返回新函数的this指向被绑定了,不能再被更改
    新函数的this指向在修改原函数this指向时就已经被绑定,一旦被绑定不能再次修改
    总结:调用函数的bind方法,会生成新的函数,绑定的this指向是针对新函数的,新函数this指向被绑定后,不能再继续被绑定(call和apply也不行);如果调用时再传入新的参数,会将新的参数和被绑定的参数进行合并,被绑定的参数会一直存在;而原函数的this指向一直没有变,还可以继续调用bind方法,生成新的函数,同时给新的函数绑定新的this指向
<script>
function fn(a,b){
    console.log(this,arguments);
}
//直接调用函数,this指向window
console.log("直接调用函数,this指向window:");
fn(1,2);//window
 
//使用函数的bind方法
console.log("使用函数的bind方法,返回新的函数:");
var fn2 = fn.bind(document,3,4);
console.log(fn2 == fn);//false 新函数和旧函数不是同一个
 
console.log("原函数的this指向:");
fn(5,6);//原函数的this指向不变,依然是window,且还可以继续调用bind方法
 
console.log("新的函数的this指向:");
console.log("如果新的函数调用时传入新的参数,会将绑定的参数和新传入的参数进行合并:");
fn2(7,8);//3,4,7,8  新函数的this指向即原函数绑定的this指向
 
//新函数的this指向在修改原函数this指向时就已经被绑定,一旦被绑定不能再次修改,且被绑定的参数也不能再被修改
//只是如果调用新函数时传入新参数,会合并两次的参数
console.log("新函数的this指向再修改原函数this指向时就已经被绑定,一旦被绑定不能再次修改:");
fn2.call(window,9,0);//这里即使再次更改this指向,fn2新函数的this指向永远不会再改变
 
//再次调用fn的bind方法
console.log("再次调用fn的bind方法,返回新的函数:");
var fn3 = fn.bind(document.body,'a','b');
console.log(fn3 == fn);
fn3('c','d');
</script> 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值