1

基本认识

数据类型

基本数据类型

string, number, null, boolean, undefined

引用数据类型

object: [],{},/\d/,Date
function

函数类型

  1. 开辟内存空间

  2. 把函数中的代码当作字符串先存储

  3. 把内存地址复制给当前函数名

JS引擎

当浏览器加载HTML页面时候,首先会先提供一个供全局JS代码执行的环境 --> 全局作用域 (globalwindow

函数先把代码体当作字符串,存储到堆内存,引用赋值给当前函数名。
执行会开辟栈,来执行当前代码。

clipboard.png

预解析

声明declare:告知浏览器在全局作用域中有一个 变量名 为 xxx 的变量。(如果一个变量只是声明了但是没有赋值,默认值:undefind)
定义defined:对变量进行赋值

预解析

在当前的作用域中,JS代码执行之前,浏览器首先会默认的把所有的带var,和function的进行提前的声明或定义

var 和 function 关键字

对于带var 和 function 关键字的在预解析的时候操作
var -> 在预解析的时候只是提前声明了
function -> 在预解析的时候提前的声明了 + 定义 都完成了。

预解析只发生在当前的作用域下进行解析。
一开始只会对widnow环境下的进行预解析,只有函数执行的时候才会对函数中的进行解析

clipboard.png

JS内存的分类

根据作用分类

栈内存: 用来提供一个供JS代码执行的环境 --> 作用域 (全局作用域/私有的作用域,和函数)
堆内存: 用来存储引用数据类型的值 --> 对象存储的是属性名和属性值,函数存储的是代码字符串

作用域链

区分私有变量和全局变量

  1. 在全局作用域下声明(预解析的时候)的变量是全局变量

  2. 在私有作用域中声明的变量和函数形参都是私有变量.

  3. 在私有作用域中,代码执行的时候遇到了一个变量,首先需要确定是否为私有变量,如果是私有变量,那么和外面的作用域没有任何关系;

函数执行

当函数执行的时候(直接目的:让函数体中的代码执行),首先会形成一个新的私有作用域。
执行代码的就是栈内存,作用域也是栈内存。

  1. 如果有形参,先给形成赋值

  2. 进行私有作用域预解析

  3. 私有作用域的代码从上至下执行

函数形成一个新的私有作用域保护了里面的私有变量不受外界的干扰。(外边变量修改不了私有变量的,私有变量的也修改不了外边变量)

作用域链

如果不是私有变量,则往当前的上级作用域进行查找,如果上级没有则继续查找,则一直找到顶级作用域(window)。

clipboard.png

全局变量的细节

在全局作用域中,带var和不带var的关系

区别:带var的可以进行预解析,所以在赋值的前面执行不会报错;
不带var的是不能进行预解析的,在前面使用该变量名会报错。

场景在全局作用域下,带var和不带var的关系:

console.log(num); // undefiend
var num = 10;    

console.log(num2); // 报错
num2 = 10;

关系:先判断是否是全局变量,如果是就输出,如果不是会再去判断是否是window底下的属性。
num2=10; --> 相当于给window增加了一个叫做num2的属性名,属性值是10
var num=10; --> 首先它相当于给全局作用域增加了一个全局变量num,但是不仅如此,也相当于给window增加了一个属性num,属性值10

var num = 10;
console.log(num); // 10
console.log(window.num); // 10

num2 = 10;
console.log(num2); // 10 // window.num2
console.log(window.num2); // 10

第一种:

function fn() {
  console.log(total); // 报错
  total = 100;
}
fn();
console.log(total); // 100

第二种:

function fn() {
  total = 100;
}
fn();
console.log(total); // 100

私有作用域中出现的一个变量不是私有的,则往上级作用域进行查找,上级没有则继续向上寻找,一直找到window为止,如果window下也没有,分为两种情况:

  1. 获取值:console.log(total); --> 报错(JS中,如果在不进行任何特殊处理的情况下,报错之后,代码退出执行)

  2. 设置值: total = 100; --> 相当于给window增加了一个属性名total,属性值为100

预解析机制

in操作符:"num" in window 判断num是否为window这个对象的一个属性,是的话返回true,不是返回false

预解析的时候不管你的条件是否成立,都要把带var的进行提前声明

if (!('num' in window)) { // 预解析,var num --> window.num  // 'num' in window 为true
  var num = 12;
}
console.log(num);  // undefined

函数表达式

匿名函数表达式:把函数定义的部分当作一个值赋值给变量/元素.

fn(); // 报错
var fn = function() { // 匿名函数之函数表达式 // 把函数定义的部分当作一个值赋给变量/元素的某一个事件
  console.log('ok');
}
fn(); // ok


fn(); // ok
function fn() {
  console.log('ok');
}
fn(); // ok

自执行函数

自执行函数:定义和执行一起完成

自执行函数在全局作用域下不进行预解析,当代码执行到当前位置的时候定义和执行一起完成了。

(function(num) {})(100);
~function(num) {}(100);
!function(num) {}(100);
+function(num) {}(100);
!function(num) {}(100);

return 后续的语句

函数体中reutrn后续的代码虽然不在执行了,但是需要进行预解析。
return 的函数是返回值,并不需要预解析。

function fn() {
  console.log(num); // undefined
  return function () { console.log(this); } // return 跟着的语句不会预解析
  var num = 100; // var 进行预解析
}

var t = fn();
console.log(t());

函数权重高

在JS中如果变量名和函数名重复,是冲突。函数级别高,使用的是函数。

console.log(fn);
function fn() {
}
var fn = 10;

在预解析的时候,如果名字已经声明过了,不需要重新声明,但是需要重新赋值.

// 声明 + 定义: fn: oxfff11
// 声明:var fn;(不需要重新声明)
// 声明 + 定义: fn: oxfff222
// ---> fn: oxfff222
fn(); // 2
function fn() {console.log(1);}
fn(); // 2
var fn = 10; // 修改作用域值
fn(); // 报错
function fn() {console.log(2);}
fn(); // 停止执行

查找作用域

如何查找当前作用域的上级作用域

看当前函数是在哪个作用域下定义的,那么它的上级作用域就是谁。(和函数在哪里执行的没有任何的关系)
函数作用域,查询变量,定义的位置有关.

var num = 12;
function fn() {
  var num = 120;
  return function() {
    console.log(num);
  }
}

var f = fn();
f(); // 120  // window环境下执行,先找父级,后找window  
// 匿名函数也是堆内存。

~function() {
  var num = 111;
  f();
}();

clipboard.png

内存释放&作用域销毁

堆内存释放

堆内存作用:存放引用内存的属性值
释放方法:null空对象指针

对象数据类型或者函数数据类型在定义的时候首先都会开辟一个堆内存,
堆内存有一个引用的地址,如果外边有变量知道了这个地址,这个内存就被占用,不能销毁。

var obj1 = {name: 1};
var obj2 = obj1;
// 想让堆内存释放/销毁,只需要把所有的引用它的变量赋值为null即可。如果堆内存没有任何东西被占用了,那么浏览器会在空闲的时候,会把它销毁。
// 垃圾回收
obj1 = null;
obj2 = null;

栈内存释放或作用域销毁

栈内存作用:存放作用域

作用域:
全局作用域(浏览器天然开辟,浏览器关闭的时候才释放)
私有作用域(只有函数执行,才会产生私有做作用域)

销毁情况

一般情况下,函数执行会形成一个新的私有的作用域,当私有作用域中的代码执行完成后,当前作用域都会主动进行释放和销毁。
作用域有被子级作用域引用,父级的私有变量不能销毁。

  1. 函数执行返回了一个引用数据类型值,并且在函数外边一个变量接收,这种情况下一般形成的私有作用域都不能被销毁。(闭包)

  2. 在一个私有的作用域中,给DOM元素的事件绑定方法,一般情况下,私有作用域都不会销毁。( DOM对象,在执行性函数中被引用)

  3. 返回的回调函数再次被执行,不会被立即销毁内存 (不立即销毁)

// 1: 引用类型的返回值,被外界引用
function fn() {
  var num = 10;
  return function() {

  }
}
var f = fn();

// 2:  DOM对象,在执行性函数中被引用
var oDiv = document.getElementById('div1');
~function() {
  oDiv.onclick = function() {}
}(); // 当自执行函数形成的这个私有作用域也不销毁


// 3: 返回的回调函数再次被执行,不会被立即销毁内存  (不立即销毁)
function fn() {
  var num = 10;
  return function() {

  }
}
fn()(); // 首先fn执行,返回一个回调函数对应的内存地址,然后紧接着让返回的小函数再执行。
// 返回的回调函数再次被执行,不会被立即销毁内存,当返回的回调函数执行完成后,浏览器会在空闲的时候销毁。

function fn() {
  var i = 10;
  return function(n) {
    console.log(n + (++i));    
  }
}

var f = fn(); 
f(10); // 21
f(20); // 32
fn()(10); // 21
fn()(20);  // 31

clipboard.png

This关键字

在JS中主要研究都是函数中的this

JS中的this代表的是当前行为执行的主体(方法,函数,事件...)
JS中的上下文(context)代表的是当前行为执行的环境(区域)
例如: 小明在沙县小吃吃蛋炒饭。 this->小明context->沙县小吃
小明可以在沙县小吃吃,也可以在其他地方吃。上下文环境可以变化。而吃的主体小明是不变的。
thiscontext是没有必然联系。

this是谁,和函数在哪里定义,在哪里执行都没有任何的关系。和执行主体有关系

如何判断执行主体?

函数执行,首先看函数名前面是否有.,有的话,.前面是谁,this就是谁;没有的话this就是window

function fn() {
  console.log(this);
}
var obj = {
  fn: fn
}
fn(); // fn中的this, window
obj.fn(); // fn中的this, obj

function sum() {
  fn(); // fn中的this,window
};
sum();

var o = {
  sum: function() {
    fn(); // fn中的this,window
  }
}
o.sum();

自执行函数中的this,指代window

给元素的某一事件绑定方法,当事件触发的时候,执行对应的方法,方法中的this是当前的元素.

document.getElementById('div1').onclick = function() {
  console.log(this); // 当前DOM对象
}

var num = 20;
var obj = {
  num: 30,
  fn: (function(num) {
    this.num *= 3;
    num += 15;
    return function() {
      this.num *= 4; 
      num += 20;
      console.log(num);
    }   
  })(num)
};

var fn = obj.fn; 
fn(); // window,// 60 * 4 // 35 += 20 // 55
obj.fn(); // 75
console.log(window.num, obj.num); // 240, 120

clipboard.png


alogy
1.3k 声望121 粉丝

// Designer and Developer