加入收藏 | 设为首页 | 会员中心 | 我要投稿 PHP编程网 - 钦州站长网 (https://www.0777zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > Asp教程 > 正文

ASP 变量 js重点知识总结

发布时间:2023-02-07 13:34:55 所属栏目:Asp教程 来源:
导读:  本文主要介绍

  立即执行函数:又叫自执行函数,定义即执行变量提升:Hoisting作用域内提升闭包:closure 一个可以访问私有作用域的函数及其所在的运行环境的组合使用闭包定义私有变量:变量私有化模块化:
  本文主要介绍
 
  立即执行函数:又叫自执行函数,定义即执行变量提升:Hoisting作用域内提升闭包:closure 一个可以访问私有作用域的函数及其所在的运行环境的组合使用闭包定义私有变量:变量私有化模块化:作用域独立化及私有化柯里化:定义多参数函数增加函数使用灵活性构造函数:又叫工厂函数,能产生隔离作用域,为生成具有特定功能的实例原型 prototype: 实例对象的共享属性,常为方法自定义对象:产生隔离作用域,为实现某些特定功能而定义的对象apply, call 和 bind方法:他们的异同及如何使用Memoization:优化耗时计算方案,常用作处理递归缓存函数重载: 允许函数有不同输入,并返回不同的结果1. 立即(自)执行函数(IIFE)
 
  立即执行函数(Immediately Invoked Function Expression)即(1)定义一个匿名函数,(2)马上调用该匿名函数。 它没有绑定任何事件也无需等待任何异步才做,即可立即执行。用过JQuery的都知道,JQuery开篇用的就是立即执行函数。
 
  立即执行函数的好处在于能隔离作用域,并在私有作用域中执行逻辑,避免了变量污染和命名冲突。常见的两种写法如下
 
  // 括号包围函数体
  (function(name) {
    let greet="Hello";
    let sayHi = () => console.log(`${greet} ${name}`)
    sayHi()
    // ...
  })('zfs')
   // 括号包围全部内容
  (function() {
    let name="liz"
    console.log(name)
    // ...
  } ())
  基础谁都懂,但我想知道的更多一些。看一题经典面试题
 
  for(var i = 0; i < 10; i++){
    setTimeout(function() {
      console.log(i);   // 为什么输出了十个10,而不是0-9
    }, 0)
  }
  【原来如此】
 
  为什么执行上述代码输出了十个10?其实想明白了就很容易了。
 
  Javascript是单线程的,执行顺序由上而下,而setTimeout是典型的异步方法,其中的操作会被挂起,直到主队列中的代码执行完成后才开始执行。
 
  又因为在for循环中,变量i是用具有变量提升效果的var定义的,因此i的作用域覆盖全局。
 
  案例中,每次循环结束,都有一个console.log()操作被挂起,当十次循环执行结束后,变量i已经累计到10(注意不是9),此时退出循环主线程执行结束,开始执行挂起队列中的打印逻辑,然而打印逻辑中的参数i已经是10,因此每次打印出了十个10
 
  那这个问题怎么处理?其实办法有很多,如把var改成ES6中没有变量提升效果块作用域定义法的let即可,每次循环执行时因i为块作用域变量因此它的值都会被保留而不会被下次循环执行覆盖。主要利用了保护执行时环境的思想。而这种思想使用立即执行函数也能实现
 
  // 注意要在被挂起之前保存执行环境否则就无效了,因此用IIFE包住异步函数
  for (var i = 0; i < 10; i++) {
    (function(ii){
      setTimeout(function(){
        console.log(ii)
      }, 0)
    })(i)
  }
  2. 变量提升 [Hoisting]
 
  这是一个相对简单但又容易踩坑的地方。在ES6之前,所谓的变量提升即 JS会将所有的变量和函数声明移动到它所在作用域的最前面。这里有两个重要的信息:
 
  (1)只将变量或函数的声明提前,而赋值并未被提前
 
  (2)只提前到变量或函数所在作用域的最前面,而不是全局作用域的最前面
 
  知道这两个要点,就不会再踩坑了。另外ES6中的let和const具有TDZ(暂时死区)的效果ASP 变量,不再本次讨论范围内。拿个案例来加深一下印象
 
  console.log(a)  // ReferenceError: a is not defined
  (function() {
    console.log(a);  // undefined
    say();  // NaN
    var a = 10;
    function say() {
      var b = 15;
      console.log(a + b)
    }
    console.log(a);  // 10
    say();  // 25
  })()
  我们改一下say函数的声明方式看一下:
 
  console.log(a)  // ReferenceError: a is not defined
  (function() {
    console.log(a);  // undefined
    say();  // Uncaught TypeError: say is not a function
    var a = 10;
   var say = function () {
      var b = 15;
      console.log(a + b)
    }
    console.log(a);
    say();
  })()
  【原来如此】
 
  因为立即执行函数具有隔离作用域的作用,而外部未定义有变量a,因此第一行执行会报错。
 
  在自执行函数作用域内,定义有var a = 10,由于变量提升效果,a的声明会被提前至函数作用域的最上方,但赋值不会被提升,因此第二个a打印undefined.
 
  函数提升效果同理,但在执行第一个say()时,因为此时的a还是没有赋值,还是undefined,因此输出NaN
 
  最底下两个打印正常不解释
 
  为什么改一下say函数的声明方式就报错了呢?
 
  其实第一段代码的执行过程
 
  console.log(a)  // ReferenceError: a is not defined
  (function() {
    var a;
    function say() {
      var b = 15;
      console.log(a + b)
   }
    console.log(a);  // undefined
    say();  // NaN
    a = 10;
    console.log(a);  // 10
    say();  // 25
  })()
  第二段代码执行过程:
 
  console.log(a)  // ReferenceError: a is not defined
  (function() {
    var a;
    var say;
    console.log(a);  // undefined
    say();  // Uncaught TypeError: say is not a function   say此时等于undefined
 
    a = 10;
    say = function () {
      var b = 15;
      console.log(a + b)
    }
    console.log(a);
    say();
  })()
  3. 闭包 [closure]
 
  所谓的闭包是指能访问私有作用域的函数及创建该函数的词法环境的组合, 这个环境包含了这个闭包创建时所能访问的所有局部变量。第一次看到这句话的时我的内心是拒绝的,不仅难理解,还绕口很难读。但这个东西还真的挺重要。到底什么意思?结合一个案例来理解
 
  $ 闭包有什么特性?
 
  function func(){
    var n = 0;  // n是func函数的局部变量
    console.log(n,'func');//
    function closure() {  // closure是func函数的内部函数,是闭包
       n += 1;  // 内部使用了外部函数中的变量n
      console.log(n,'closure');
    }
    return closure;
  }
  var counter= func();//这里会调用一次func函数,打印  0,'func'
  counter();  //调closure 打印 1,'closure'
  counter();  //调closure 打印 2,'closure'
  counter();  //调closure 打印 3,'closure'
  func只有在var counter= func()时调用了一次,后续counter()就只会调用closure函数,func不会再执行了。
 
  这里利用闭包实现了一个计数器功能,它有什么好处?相比于普通变量定义的计数器,这个计数器只能通过调用counter来实现数量n的累加,再没有别的方法可以改变n的值,这样就保证了计数器正确稳定的计数杜绝外部干扰和破坏。这是闭包的一大优势。为什么有这效果?听说会造成内存泄漏是怎么回事?
 
  我们得先理解闭包定义的这句话。分析一下案例,func是一个普通函数,在它的私有作用域中,定义了一个变量n,和一个closure函数,最后将这个函数返回。
 
  单从func出发,在其私有作用域中有两种性质的变量或方法:(1)私有的即外部不可访问的;(2)被暴露可访问的即被return的。关于这点一会模块化里还会再提到。
 
  而闭包要做的事就是将函数私有作用域内外部不可访问的变量或方法让外界可以访问,实现这个思想的主要手段就是通过被暴露的方法来导出私有变量,这样做是为了避免变量污染全局同时也避免被污染,同时又可以在全局中被引用和改变更新,通过这种方式定义的变量来做以上计数器的功能,是很被放心的。
 
  然而闭包虽好用,却容易造成内存泄漏问题。浏览器自身有垃圾回收机制(GC),即引用数为0的变量或方法所占用的内存空间会被释放回收。通常情况下,一个普通函数执行结束后,因为其私有作用域效果,其内部的所有变量和函数的引用数立刻下降为0,GC会立刻回收这部分内容。 而闭包因为其特性,即使本函数执行结束,也可能有某个外部方法仍然调用着内部的变量导致引用数不为0,造成GC无法回收内存也就是所说的内存泄漏
 
  回到案例,当执行完第一个counter()打印1后,我们理解函数执行完毕,变量n应该被GC回收而不存在,但当我们继续执行第二个counter()后发现,打印值是2不是1,也就是说,这个func的私有变量n一直被counter引用着,因此实现了计数累加。这也就是产生内存泄漏的根源所在。
 
  关于闭包还可查阅闭包攻略一文
 
  4. 使用闭包定义私有变量
 
  通常开发者们会用下划线定义私有变量,但从严格意义上来说这并不准确。闭包能真正做到定义私有变量
 
  function Product () {
    var name;   // 函数内的私有变量
    this.setName = function (v) {
      name = v;
    };
    this.getName = function () {
      return name;
    };
  }
  var person = new Product();
  person.setName('zfs');
  console.log(person.name);   // undefined
  console.log(person.getName());  // ‘zfs’
  【原来如此】
 
  name是函数内的私有变量,外界不可访问,因此person.name()为undefined,而getName()在函数内部能访问到name值,因此得到zfs。
 
  这很像一个工厂函数,只是工厂函数通常都会将所有的变量和函数都暴露出来,当然这也不是绝对的,我们同样可以认为这就是一个工厂函数
 
  5. 模块化
 
  在ES6之前,JavaSrcipt并不是模块化语言,后来有了AMD(Asynchronous Module Definition异步模块定义)规范和RequireJs规范,使得JS也能实现模块化编程。直到2015年ES6的出现,JS的模块化变得更加重要和流行。想了解更多模块化知识,可阅读模块化编程 一文
 
  定义一个模块的方式有很多种,既然要成为一个模块,它必就必须要有私有作用域。可以实例化一个对象,用一个独立的文件,或者一个立即执行函数等方法来实现。借鉴大家所熟知的JQuery库,有如下案例
 
  var module = (function() {
    let _count = 3;
    function print () {
      console.log(`now count is ${_count }`)
    }
    function plus (x = 1) {
      _count = x + _count
      print(x)
    }
    return {
      desc: 'this is a module sample',
      plus: plus
    }
  })();
  console.log(module.desc);  // "this is a module sample"
  module.plus(2);  // now count is 5
  【原来如此】
 
  稍有用心你会发现,一个模块通常会有几个特性:(1)有个独立的作用域空间,形成模块;(2)有一些私有的方法或变量,用于处理模块内的逻辑,而这些逻辑对外界透明外界也不需要关心;(3)会暴露出一些方法或变量,用于提供外界访问的接口。
 
  模块化的最优实现方案是控制好自己的私有作用域,隐藏外界不需要关心的变量和处理逻辑,同时不被污染和意外的修改,这与闭包思想一致。如下案例便不是一个好的办法
 
  var module = new Object {
    _count: 3;
    f1: function(){
      console.log('I am f1');
    }
  }
  console.log(module._count);  // 3
  module.f1();  // I am f1
  对象定义法实现简单,但暴露了所有成员变量,外界可以很随意的通过module本身调用和修改更新内部的成员值。导致模块内部的变量不安全,不是最佳方案
 
  6. 柯里化
 
  柯里化,即 Currying,目的是为了提高函数的灵活性。常规的函数只有一个参数,在学习了闭包的思想后我们得知可以在函数内部返回一个函数,但我们可以一次性传入多个参数,方案如下
 
  var plus = function (a) {
    return function (b) {
      console.log(a + b);
    }
  }
  var add5 = plus(5);
  var add8 = plus(8);
  plus(20)(15);  // 35
  add5(10); // 15
  add8(10);  // 18
  看完案例是不是觉得很简单?定义一个类似plus函数我们可以先传入一个参数,处理一些公用的基础的逻辑,得到一个带有功能的函数体,在用第二个参数来处理各个业务流程不同的需求。避免了重复实现一些基础逻辑的部分。
 
  7. 构造函数
 
  JS中不存在类,至少在ES6之前是不存在的。此之前,实现这个相应的功能则是通过构造函数和原型链来实现的。
 
  通常实现一个构造函数有一下几个特点:
 
  (1)构造函数函数名首字母建议(必须)大写,用来区分与普通函数的区别。
 
  (2)内部属性使用this来指向即将要生成的属性;
 
  (3)使用new关键字来生成实例对象
 
  var Person = function () {
    this.name="zfs";
    this.age = 25
    this.intro = function () {
      console.log(this.name + ' age '  +  this.age)
    }
  }
  var p1 = new Person()
  console.log(p1.name)  // zfs
  p1.intro ()  // zfs age 25
  所有的实例对象都可以继承构造器函数中的属性和方法,但是,不同实例对象之间的属性和方法相对独立,无法共享数据。要解决这个问题,需要使用到构造函数的prototype原型属性
 
  8. 构造函数的原型 prototype
 
  先思考一个问题: let obj = Product() 和 let obj = new Product() 这两者怎么理解?有什么区别?
 
  前者是将函数Product的运行返回值赋给变量obj,后者做的是 调用构造函数,创建一个包含prototype内部指针的新对象obj。
 
  实际上:每个JavaScript构造函数都有一个prototype属性,用于设置所有实例对象需要的共享属性和方法。被声明在prototype中的方法和属性不能被枚举,也不能通过 hasOwnProperty() 判断,判断对象是否含有某个原型属性需要使用in关键字
 
  关于prototype个人认为W3C的说法并不太容易理解反而有点容易混淆,如下
 
  prototype 属性使您有能力向对象添加属性和方法。
 
  语法: Object.prototype.name = value
 
  相信很多新手朋友会有如下误解
 
  var obj = {
    name: 'zfs',
    age: 25
  }
  obj.prototype.hobby = 'basketball'
  // TypeError cannot set property `hobby` of undefined
  没错啊,根据W3C的讲解,我给对象obj设置了原型属性hobby,为什么报错了?
 
  会这么做的朋友,大都是没有好好理解W3C语法那半句。没有注意到语法中所谓的对象,其实是“Object”,它是什么?原生能力较好的朋友应该都清楚,这其实是对象的构造器,我们可以通过它来实例化出实例对象,即
 
  let obj = new Object()
  prototype是构造函数上用来设置共享属性和方法的属性,上述错误主要是错在将prototype设置在了实例对象上了,因此编译器抛出异常。正确的用法应该是如下所示:
 
  var someone = {}
  // 需要在构造函数上设置原型属性
  Object.prototype.hobby = 'basketball'
  // in 能遍历出原型属性,而hasOwnProperty() 则不能
  for (let s in someone) {
    console.log(s)  // hobby
  }
  prototype在构造函数上的应用,更加丰富和值得大家学习掌握
 
  // 创建构造函数
  function Person (name="sg") {
    this.name = name;  // 实例属性 name
    this.visited= [];  // 实例属性 travel
    this.sayHi = function () {  // 实例方法 sayHi()
      console.log(`Hello, ${name}`)
    }
  }
  Person.prototype.city = 'beijing'  // 原型属性 city
  Person.prototype.travel = function (place) {
    this.visited.push(place)
    console.log(this.visited)
  }
  let zfs = new Person('zfs')
  let borui = new Person('borui')
  console.log(zfs.name)  // 'zfs'
  zfs.sayHi()  // Hello, zfs
  console.log(zfs.city);  // beijing
  zfs.travel('shanghai'); // ['shanghai']
  console.log(borui.name)  // borui  实例属性值各自私有
  console.log(borui.city)  // beijing 实例属性被共享
  zfs.travel('chongqin')  // ['shagnhai', 'chongqin']
  // 原型共享属性中,当其中一个实例对象的属性值被修改,不会影响其他实例对象。
  zfs.city = 'fujian';
  console.log(zfs.city);  // fujian
  console.log(borui.city);  // beijing
  总结一下: prototype是设置在构造函数中的用来创建共享属性和方法的特殊属性,用它设置的属性和方法可以被所有的实例对象继承,不同实例对象中的属性修改不会相互影响。
 
  【扩展】
 
  另外,JS属性大家还应该知道三个:(1)静态属性;(2)实例属性;(3)原型属性
 
  // 静态属性
  function Person () { }
  let zfs = new Person()
  Person.age="25"
  // 静态属性只能通过 `类名.属性` 形式来访问,无法通过实例访问
  console.log(Person.age)  // 25
  console.log(zfs.age)  // undefined
  // 实例属性
  funciton Person () {
    this.name="zfs";  // 用this 引用,能够被实例对象直接继承的属性
  }
  function Person() { }
  Person.prototype.name="zfs";  // 使用原型定义的属性称为共享属性
 

(编辑:PHP编程网 - 钦州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章