一些JS超级基础的知识梳理(五)

作用域、变量提升、闭包、原型模式与原型链、面向对象

1. 作用域

作用域分为全局作用域和私有作用域

全局作用域:当浏览器打开页面时,会形成一个供代码执行的运行环境,这个环境叫全局作用域,全局作用域一个页面只有一个,同时也会形成两个虚拟的内存,一个是栈内存,一个是堆内存

  1. 在全局作用域形成以后,在这个全局作用域会默认提供最大的 winidow 对象 ,当调用 window 下的方法时,window 可以省略.

私有作用域: 是给函数体中的代码提供代码的运行环境的
2. 私有作用域是在全局作用域下形成创建的;在全局作用域中包含私有的作用域
3. 全局作用域不能访问私有作用域下的私有变量,但是私有作用域能够访问全局作用下变量
4. 如果私有作用域存在该私有变量,那么就不再向外获取

ps: 栈内存(提供代码的运行环境,存储基本数据类型)
堆内存(存储引用数据类型值)

2. 变量
  1. 全局变量:在全局作用域下定义的变量叫全局变量,同时会默认给 window 新增一个属性名,属性名是变量名,属性值是变量所存储的值。如 b = 1 也是在给 window 新增键值对
  2. 在函数私有作用域中定义的变量,不会给 window 新增键值对,函数里可以访问函数外面的变量
  3. 函数执行会形成私有的作用域,会保护里面的私有变量不受外界的干扰
  4. 在全局作用域下定义的函数,也会给 window 新增键值对;属性名是函数名,属性值是函数的堆内存地址
function bar() {
    c = 100; // 在函数里面,也会给 window 新增键值对;
    function foo() { // 在函数体中函数不会给 window 新增键值对,函数外面不能调用此函数;
    }
    foo();
}
bar();
  1. 私有变量的几种情况
    • 在函数体中被 var 过,这就是私有变量
    • 在函数体中被 function 的也是私有变量
    • 形参也是私有变量
    • 在函数体中,被 var,被 let,被 const 都是私有的
    • 把所有带 var 和带 function 关键字进行提前的声明和定义 => 变量提升
3、let / const 和 var 的区别
  1. let 和 const 不存在变量提升体制
  2. var 允许重复声明,而 let 是不允许的
  3. let 能解决 typeof 检测时出现暂时性死区问题(let 比 var 更严谨)
4、闭包作用域
  1. 创建函数
    函数执行形成私有作用域,在私有作用域中变量不受外界影响,我们称这种机制为闭包。其中最经典的情形是不销毁的栈内存,例如一个函数执行 return 一个函数。
    • 开辟一个堆内存
    • 把函数体中的代码当作字符串存储进去
    • 把堆内存的地址赋值给函数名 / 变量名
    • 函数在那创建,那么它执行时所需要查找的上级作用域就是谁
  2. 函数执行
    • 开辟一个栈内存
    • 形参赋值 && 变量提升
    • 代码执行(把所属堆内存中的代码字符串拿出来一行行执行)
    • 遇到一个变量,首先看它是否为私有变量(形参和在私有作用域中声明的变量是私有变量),是私有的就操作自己的变量即可,不是私有的则向上级作用域中查找…一直找到全局作用域为止, =》作用域链查找机制
    • 私有变量和外界的变量没有必然关系,可以理解为被私有栈内存保护起来了,这种机制其实就是闭包的保护机制
  3. 关于堆栈内存释放问题(以谷歌 webkit 内核为例子)
    函数执行就会形成栈内存(从内存中分配一块空间),如果内部都不销毁释放,很容易就会导致栈内存溢出(内存爆满,电脑就卡死了),堆栈内存的释放问题是学习 JS 的核心知识之一
    • 堆内存释放问题
// 创建一个引用类型值,就会产生一个堆内存
// 如果当前创建的堆内存不被其他东西所占用了,(浏览器会在空闲的时候,查找每一个内存的引用状况,不被占用的都会给回收释放掉)则会释放
   let obj = {
       name : 'zhufeng'
   };
   let oop = obj;
   // 此时 obj 和 oop 都占用着对象的堆内存,想要释放堆内存,需要手动解除变量和值的关联(null:空对象指针)
   obj = null;
   oop = null;

• 栈内存释放

// 打开浏览器会形成的全局作用域是栈内存
// 手动执行函数形成的私有作用域是栈内存
// 基于 ES6 中的 let / const 形成的块作用域也是栈内存
/*
  全局栈内存:页面关掉的时候才会销毁
  私有栈内存:
    1. 一般情况下,函数只要执行完成,形成的私有栈内存就会被销毁释放掉(排除出现无极限递归,出现死循环的模式)
    2. 但是一旦栈内存中的某个东西(一般都是堆地址)被私有作用域以外的事物给占用了,则当前私有栈内存不能立即被释放销毁(特点:私有作用域中的私有变量等信息也保留下来了)  => 市面上认为的闭包:函数执行形成不能被释放的私有栈内存,这样的才是闭包
     function fn(){
         // ...
     }
     fn(); // 函数执行形成栈内存,执行完成栈内存销毁
     
     function X(){
         return function(){
             // ...
         }
     }
     let f=X(); // f 占用了 X 执行形成的栈内存中的一个东西(返回小函数对应的堆),则X执行形成的栈内存不能被释放了
*/

变量提升的特殊情况

  1. 等号的右边不进行变量提升,变量提升只发生在等号的左边
  2. return下面的代码要进行变量提升;return 后面的代码是不进行变量提升的
  3. 如果函数的属性值时一个自执行函数,那么当代码以键值对存储的时候(当代码执行到这一行时,自执行函数就会运行),并且把自执行函数的执行结果赋值给属性名
  4. 如果变量名重名,不再进行重复声明,但是要重新赋值
  5. 自执行函数
  6. 给 window 新增键值对是发生在变量提升的阶段
5.原型与原型链

原型对象
原型 prototype: 每一个函数(普通函数、构造函数【类】)都天生自带一个属性 prototype(原型)。这个属性的值是一个对象,用来存储当前类型的共有的属性和方法。保存在原型上面的属性和方法称为公有属性或公有方法。
hasOwnProperty() 方法:检测某个属性是否是对象的私有属性,如果是私有属性,返回 true,否则返回false;
检测某个属性是否为对象的公有属性:hasPubProperty

原型链

  1. 先找自己私有的属性,有则调取使用,没有继续找
  2. 基于 proto 找所属类原型上的方法(Fn.prototype),如果还没有则继续基于 proto 往上找…一直找到Object.prototype 为止
  3. 如果找到了就使用,没找到返回 undefined

构造函数:构造函数也是一个函数,但是其内部实现和调用方式和普普通函数不同
1.构造函数只能用 new 来调用(用 new 调用,这个函数才叫构造函数)
2.构造函数不需要手动创建实例对象,在通过 new 调用构造函数时,构造函数会隐式创建一个属于这个类的实例对象,并且把构造函数种的 this 指向这个实例对象,后续的加工都在 this 上加工就可以了
3.构造函数不需要手动返回实例对象,构造函数在执行后会隐式的返回实例对象

柯里化函数思想:把多参数的函数变成单参数的函数

function fn(a, b, c) {
  return a + b + c;
}

function fn1(a) {
  return function (b) { // 这种在函数中 return 函数的做法是市面中认为的闭包
    return function (c) {
      return a + b + c;
    }
  }
}
fn1(1)(2)(3);

惰性封装

var utils = (function () {
  var version = '1.0.1';
  function sum(a, b) {
    return a + b
  }
  function minus(a, b) {
    return a - b;
  }
  return {
    sum: sum,
    minus: minus
  }
})();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值