首页 > 其他分享 >js高级之函数高级部分

js高级之函数高级部分

时间:2022-11-09 10:14:33浏览次数:55  
标签:function console log 高级 js 原型 var 函数

基于尚硅谷的尚硅谷JavaScript高级教程提供笔记撰写,加入一些个人理解

github源码
博客下载

原型与原型链

  • prototype : 显式原型属性,它默认指向一个Object空对象(即称为: 原型对象)
  • 原型对象中有一个属性constructor, 它指向函数对象

给原型对象添加属性(一般都是方法)

作用: 函数的所有实例对象自动拥有原型中的属性(方法)

  // 每个函数都有一个prototype属性, 它默认指向一个对象(即称为: 原型对象)
    console.log(Date.prototype, typeof Date.prototype)

    function fun() { }

    console.log(fun.prototype);// 默认指向一个Object空对象(没有我们的属性)

    //原型对象中有一个属性constructor, 它指向函数对象
    console.log(Date.prototype.constructor === Date)// true
    console.log(fun.prototype.constructor === fun)// true

    //给原型对象添加属性(一般都是方法)
    fun.prototype.test = function () {
      console.log('test()');
    }
    //创建一个实例
    var f = new fun()
    f.test() // 可以执行 

所有实例对象都有一个特别的属性

__proto__ : 隐式原型属性

显式原型与隐式原型的关系

  • 函数的prototype属性: 在定义函数时自动添加的, 默认值是一个空Object对象
  • 对象的__proto__属性: 创建对象时自动添加的, 默认值为构造函数的prototype属性值
  • 程序员能直接操作显式原型, 但不能直接操作隐式原型(ES6之前)
  • 原型对象即为当前实例对象的父对象
  • 对象的隐式原型的值为其对应构造函数的显式原型的值
    function Fn() {  // 内部语句:this.prototype={}
    }
    // 每个函数function都有一个prototype,即显式原型 
    console.log(Fn.prototype)
    // 每个实例对象都有一个__proto__,可称为隐式原型
    var fn = new Fn()  // 内部语句:this.__proto__ = Fn.prototype
    console.log(fn.__proto__)
    // 对象的隐式原型的值为其对应构造函数的显式原型的值
    console.log(Fn.prototype === fn.__proto__)// true
    //给原型添加方法
    Fn.prototype.test = function () {
      console.log('test()')
    }
    fn.test()

原型链

基础理解

  • 所有的实例对象都有__proto__属性, 它指向的就是原型对象
  • 这样通过__proto__属性就形成了一个链的结构---->原型链
  • 当查找对象内部的属性/方法时, js引擎自动沿着这个原型链查找
  • 当给对象属性赋值时不会使用原型链, 而只是在当前对象中进行操作

访问一个对象的属性时

  • 先在自身属性中查找,找到返回
  • 如果没有, 再沿着__proto__这条链向上查找, 找到返回
  • 如果最终(Object原型对象)没找到, 返回undefined
  • 别名: 隐式原型链
  • 作用: 查找对象的属性(方法)

构造函数/原型/实体对象的关系(图解)

实例对象的隐式原型指向构造函数的显示原型

构造函数/原型/实体对象的关系2(图解)

函数的显示原型指向的对象默认是Object实例对象(但Object不满足)

    console.log(Fn.prototype instanceof Object);// true
    console.log(Object.prototype instanceof Object);// false
    console.log(Function.prototype instanceof Object);// true

函数实际上是Function的实例对象(包括Function本身),所以函数既有隐式原型也有显示原型

所有函数的隐式原型都指向Function的显示原型

    function fun1() { }
    function fun2() { }
    console.log(fun1.__proto__ === Function.prototype);// true
    console.log(fun2.__proto__ === Function.prototype);// true
    console.log(fun2.__proto__ === fun1.__proto__);// true

Function本身的隐式原型也指向本身的显示原型

    console.log(Function.prototype === Function.__proto__);// true

Object的原型对象是原型链的尽头

    console.log(Object.prototype.__proto__)// null

原型链属性问题

  • 读取对象的属性值时: 会自动到原型链中查找
  • 设置对象的属性值时: 不会查找原型链, 如果当前对象中没有此属性, 直接添加此属性并设置其值
  • 方法一般定义在原型中, 属性一般通过构造函数定义在对象本身上
    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    Person.prototype.setName = function (name) {
      this.name = name;
    }
    Person.prototype.sex = '男';

    var p1 = new Person('Tom', 12)
    p1.setName('Jack')
    console.log(p1.name, p1.age, p1.sex)// Jack 12 男
    p1.sex = '女'
    console.log(p1.name, p1.age, p1.sex)// Jack 12 女

    var p2 = new Person('Bob', 23)
    console.log(p2.name, p2.age, p2.sex)// Bob 23 男

instanceof

表达式: A instanceof B

如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false

Function是通过new自己产生的实例

案例一

如果B函数的显式原型对象在A对象的原型链上, 返回true, 否则返回false

这里很简单可以看出,Foo构造函数的显示原型与实例f1,f2的隐式原型指向同一个对象,所以instanceof判断为true

    function Foo() { }
    var f1 = new Foo();
    console.log(f1 instanceof Foo);// true
    console.log(f1 instanceof Object);// true

案例二

    //Object函数是function的实例,所以有隐式原型
    console.log(Object instanceof Function)// true
    console.log(Object instanceof Object)// true
    console.log(Function instanceof Object)// true
    //Function是通过new自己产生的实例
    console.log(Function instanceof Function)// true
    function Foo() { }
    console.log(Object instanceof Foo);// false

面试题一

注意构造函数的显示原型指向的对象发生了变化

 var A = function () {
    }
    A.prototype.n = 1
    var b = new A()
    A.prototype = {
      n: 2,
      m: 3
    }
    var c = new A()
    console.log(b.n, b.m, c.n, c.m)// 1 undefined 2 3

面试题二

建议自己画图,画图比较清晰

注意点:找原型链是从隐式原型开始

    var F = function () { };
    Object.prototype.a = function () {
      console.log('a()')
    };
    Function.prototype.b = function () {
      console.log('b()')
    };
    var f = new F();
    f.a() // a() f.__proto__===F.prototype,F.prototype.__proto__===Object.prototype
    // f.b() // b is not a function
    F.a() // a() F.__proto__===Function.prototype,Function.prototype.__proto__===Object.prototype
    F.b() // b() F.__proto__===Function.prototype

执行上下文与执行上下文栈

  • 执行上下文: 由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
  • 执行上下文栈: 用来管理产生的多个执行上下文

变量提升与函数提升

  • 变量提升: 在变量定义语句之前, 就可以访问到这个变量(undefined)(使用var关键字定义,es6的let,const不会变量提升,现在开发很少使用var关键字,变量一般用let,对象、数组、函数使用const)
  • 函数提升: 在函数定义语句之前, 就可以执行该函数
  • 先有变量提升, 再有函数提升
    var a = 4
    function fn() {
      console.log(a)
      var a = 5
    }
    fn()// undefined
    console.log(a1) //可以访问, 但值是undefined
    a2() // 可以直接调用
    a3()// 此处遵循变量提升,不能调用
    var a1 = 3
    function a2() {
      console.log('a2()')
    }
    var a3 = function () {
      console.log('a3()');
    }

执行上下文理解

代码分类(位置)

  • 全局代码
  • 函数代码

全局执行上下文

在执行全局代码前将window确定为全局执行上下文

对全局数据进行预处理

  1. var定义的全局变量==>undefined, 添加为window的属性
  2. function声明的全局函数==>赋值(fun), 添加为window的方法
  3. this==>赋值(window)
  4. 开始执行全局代码
    console.log(a1, window.a1);//undefined undefined
    a2()//a2()
    window.a2()//a2()
    console.log(this);
    var a1 = 3
    function a2() {
      console.log('a2()');
    }

函数执行上下文

在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象

对局部数据进行预处理

  1. 形参变量>赋值(实参)>添加为执行上下文的属性
  2. arguments==>赋值(实参列表), 添加为执行上下文的属性
  3. var定义的局部变量==>undefined, 添加为执行上下文的属性
  4. function声明的函数 ==>赋值(fun), 添加为执行上下文的方法
  5. this==>赋值(调用函数的对象)
  6. 开始执行函数体代码
    function fn(a1) {
      console.log(a1);// 2
      console.log(a2);// undefined
      a3()// a3()
      console.log(this);// window
      console.log(arguments);// 伪数组(2,3)
      var a2 = 3
      function a3() {
        console.log('a3()');
      }   
    }
    fn(2, 3)

分类

全局: window
函数: 对程序员来说是透明的

生命周期

全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
函数 : 调用函数时产生, 函数执行完时死亡

执行上下文创建和初始化的过程

全局
  • ​ 在全局代码执行前最先创建一个全局执行上下文(window)
  • ​ 收集一些全局变量, 并初始化
  • ​ 将这些变量设置为window的属性
函数
  • ​ 在调用函数时, 在执行函数体之前先创建一个函数执行上下文
  • ​ 收集一些局部变量, 并初始化
  • ​ 将这些变量设置为执行上下文的属性

执行上下文栈理解

  • 在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象
  • 在全局执行上下文(window)确定后, 将其添加到栈中(压栈)
  • 在函数执行上下文创建后, 将其添加到栈中(压栈)
  • 在当前函数执行完后,将栈顶的对象移除(出栈)
  • 当所有的代码执行完后, 栈中只剩下window
                            //1. 进入全局执行上下文
  var a = 10
  var bar = function (x) {
    var b = 5
    foo(x + b)              //3. 进入foo执行上下文
  }
  var foo = function (y) {
    var c = 5
    console.log(a + c + y)
  }
  bar(10)                    //2. 进入bar函数执行上下文

面试题一

依次输出什么?

整个过程中产生了几个执行上下文? 5个

  console.log('gb: '+ i)// undefined
  var i = 1
  foo(1);// fb:1 fb:2 fb:3 fe:3 fe:2 fe:1
  function foo(i) {
    if (i == 4) {
      return;
    }
    console.log('fb:' + i);
    foo(i + 1);// 递归调用:在函数内部调用自己。每一次调用执行并没有执行完,直到i=4结束递归,重新执行后续代码
    console.log('fe:' + i);
  }
  console.log('ge: ' + i)// ge:1

面试题二

 /*
    测试题1: 先预处理变量, 后预处理函数
    */
    function a() { }
    var a;
    console.log(typeof a)// function


    /*
    测试题2: 变量预处理, in操作符
     */
    if (!(b in window)) {
      var b = 1;
    }
    console.log(b)// undefined

    /*
    测试题3: 预处理, 顺序执行
     */
    var c = 1
    function c(c) {
      console.log(c)
      var c = 3
    }
    c(2)// c is not a function

作用域与作用域链

主要是概念类的东西,也很简单,就两个一起讲

理解

  • 作用域: 一块代码区域, 在编码时就确定了, 不会再变化

    分类

    全局作用域
    函数作用域
    js没有块作用域(在ES6之前)

    • 作用域链: 多个嵌套的作用域形成的由内向外的结构, 用于查找变量

作用

  • 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
  • 作用域链: 查找变量

区别作用域与执行上下文

​ 这里的全局上下文环境中的d应该是b

  • 区别1
  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
  • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
  • 函数执行上下文环境是在调用函数时, 函数体代码执行之前创建
  • 区别2
  • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
  • 上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会自动被释放
  • 联系
  • 上下文环境(对象)是从属于所在的作用域
  • 全局上下文环境==>全局作用域
  • 函数上下文环境==>对应的函数使用域

面试题一

  /*
    问题: 结果输出多少?  10
   */
  var x = 10;
  function fn() {
    console.log(x);
  }
  function show(f) {
    var x = 20;
    f();
  }
  show(fn);

面试题二

    /*
      说说它们的输出情况
     */
    var fn = function () {
      console.log(fn)
    }
    fn()
    /* 
    function () {
      console.log(fn) 
    }
    */
    var obj = {
      fn2: function () {
        console.log(fn2)
        // 答案:输出结果 报错
        // obj对象里的方法fn2,要输出fn2,开始找这个fn2,自己所在的作用域没找到,跑外面继续找
        // 跑到全局作用域没找到,输出结果当然报错
        // 为什么直接输出fn2却找不到obj里那个fn2呢,因为fn2是属于obj的一个属性/方法,要访问到它就得obj.fn2
        // 要输出fn2函数体的话得这么写  console.log(this.fn2)
      }
    }
    obj.fn2()
  /*
        fn2 is not defined
  */

闭包

闭包引入

  <button>测试1</button>
  <button>测试2</button>
  <button>测试3</button>
  <!--
需求: 点击某个按钮, 提示"点击的是第n个按钮"
-->
  <script type="text/javascript">
    var btns = document.getElementsByTagName('button')

    //遍历加监听
    // btns是一个伪数组,btns.length需要不断计算,可以提前保存,加快执行效率
    for (var i = 0, length = btns.length; i < length; i++) {
        /*
        这里其实可以将var改成let就可以解决,但let是es6语法,我们js高级只涉及es5
        */
      var btn = btns[i]
      btn.onclick = function () {
        alert('第' + (i + 1) + '个按钮')// 无论哪个按钮都输出“第4个按钮”
      }
    }
  </script>
    for (var i = 0, length = btns.length; i < length; i++) {
      var btn = btns[i]
      btn.index = i// 解决办法  保存下标
      btn.onclick = function () {
        alert('第' + (this.index + 1) + '个按钮')
      }
    }
   // 利用闭包解决
    for (var i = 0, length = btns.length; i < length; i++) {
      (function (i) {
        var btn = btns[i]
        btn.index = i
        btn.onclick = function () {
          alert('第' + (this.index + 1) + '个按钮')
        }
      })(i)
    }

什么是闭包

如何产生闭包?

当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包

闭包到底是什么?

使用chrome调试查看

  • 理解一: 闭包是嵌套的内部函数(绝大部分人)
  • 理解二: 包含被引用变量(函数)的对象(极少数人)
  • 注意: 闭包存在于嵌套的内部函数中

产生闭包的条件?

  • 函数嵌套
  • 内部函数引用了外部函数(执行)的数据(变量/函数)
    function fn1() {
      var a = 3
      function fn2() {
        console.log(a)// fn2引用了fn1的a,因此形成了闭包
      }
        fn2()// 笔者目前是2022年,必须调用内部函数才能在chrome中看到闭包
    }
    fn1()

常用闭包

将函数作为另一个函数的返回值

    function fn1() {
      var a = 2
      function fn2() {
        a++
        console.log(a);
      }
      return fn2
    }
    var f = fn1()
    f()// 3
    f()// 4
    //f相当于fn2的实例

将函数作为实参传递给另一个函数调用

 function showDelay(msg, time) {
      setTimeout(function () {
        alert(msg)
      }, time)
    }
    showDelay('Hello', 2000)

闭包的作用

  • 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
  • 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
    function fun1() {
      var a = 3;//此处闭包已经产生
      function fun2() {
        a++;            //引用外部函数的变量--->产生闭包
        console.log(a);
      }
      return fun2;
    }
    var f = fun1();  //由于f引用着内部的函数-->内部函数以及闭包都没有成为垃圾对象
    f();   //间接操作了函数内部的局部变量
    f();
    f=null //此时闭包对象死亡

闭包的生命周期

产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)

死亡: 在嵌套的内部函数成为垃圾对象时

闭包的应用

JS模块

  • 具有特定功能的js文件
  • 将所有的数据和功能都封装在一个函数内部(私有的)
  • 只向外暴露一个包信n个方法的对象或函数
  • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能

写法一

 <script type="text/javascript" src="05_coolModule.js"></script>
  <script type="text/javascript">
    var module = myModule()
    module.doSomething()
    module.doOtherthing()
  </script>
function myModule() {
  var msg = 'My atguigu'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething ' + msg.toUpperCase());
  }
  function doOtherthing() {
    console.log('doOtherthing ' + msg.toLowerCase());
  }
  //向外暴露
  return {
    doSomething,
    doOtherthing
  }
}

写法二

<script type="text/javascript" src="05_coolModule2.js"></script>
<script type="text/javascript">
  myModule2.doSomething()
  myModule2.doOtherthing()
</script>
(function (window) {
  var msg = 'My atguigu'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething ' + msg.toUpperCase());
  }
  function doOtherthing() {
    console.log('doOtherthing ' + msg.toLowerCase());
  }
  //向外暴露
  window.myModule2 = {
    doSomething,
    doOtherthing
  }
})(window)//方便代码打包压缩

闭包的缺点

缺点

  • 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
  • 容易造成内存泄露(详见补充说明)

解决

  • 能不用闭包就不用
  • 及时释放
    function fn1() {
      var arr = new Array(100000)// 占用空间大
      function fn2() {
        console.log(arr.length);
      }
      return fn2
    }
    var f = fn1()
    f()
    f = null//解决办法,让内部函数成为垃圾对象-->回收闭包

面试题一

注意this的指向

    //代码片段一
    var name = "The Window";
    var object = {
      name: "My Object",
      getNameFunc: function () {
        return function () {
          return this.name;//这里没有闭包
        };
      }
    };
    console.log(object.getNameFunc()());  //The Window

    //代码片段二
    var name2 = "The Window";
    var object2 = {
      name2: "My Object",
      getNameFunc: function () {
        var that = this;
        return function () {
          return that.name2;
        };
      }
    };
    console.log(object2.getNameFunc()()); //My Object

面试题二

注意是否使用了新产生的闭包

  function fun(n, o) {
      console.log(o)
      return {
        fun: function (m) {
          return fun(m, n)
        }
      }
    }

    var a = fun(0)// undefined
    a.fun(1)//  0
    a.fun(2)//  0
    a.fun(3)//  0

    var b = fun(0).fun(1).fun(2).fun(3) //undefined 0 1 2

    var c = fun(0).fun(1)// undefined 0
    c.fun(2)// 1
    c.fun(3)// 1

补充

内存溢出与内存泄露

内存溢出

  • 一种程序运行出现的错误
  • 当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误(加根内存条

    标签:function,console,log,高级,js,原型,var,函数
    From: https://www.cnblogs.com/SakuraCaslana/p/16872609.html

相关文章

  • js高级之对象高级部分
    基于尚硅谷的尚硅谷JavaScript高级教程提供笔记撰写,加入一些个人理解github源码博客下载对象的创建模式Object构造函数模式套路:先创建空Object对象,再动态添加属......
  • 从6个方面净化你的Js代码
    记录一下怎样写出整洁规范的代码,用于共勉进步。对于什么是整洁的代码,书中给出了大师们的总结:BjarneStroustrup:优雅且高效;直截了当;减少依赖;只做好一件事Gradybooch:简单直接......
  • js提交数据
    一、from表单提交<formaction=""method="post"enctype="multipart/form-data"><inputtype="submit"class="btnbtn-info">属性:action:是form表单提交数据的......
  • 使用jwt鉴权(jsonwebtoken)
    1.下载jsonwebtokennpmi-Sjsonwebtoken2.引用constjwt=require('jsonwebtoken');3.需要设置秘钥constsecretKey='3.14159263528542852651268541';4.设......
  • 前端零配置打包工具 parceljs 体验
    参考https://www.parceljs.cn/getting_started.htmlparceljs中文官网https://www.parceljs.cn/getting_started.htmlParcel1版本的中文文档https://v2.parceljs.cn......
  • js小知识点
    01.thisthis的指向完全由函数在哪里调用决定。在ES5中,this永远指向调用它的那个对象;在ES6的箭头函数中没有this绑定,this指向箭头函数定义时作用域中的this;判断this的......
  • 谈谈js中this的理解
    this一、this的理解第一准则:this永远指向函数运行时所在的对象,而不是函数被创建时所在的对象【不包含箭头函数】二、this的集中指向:window对象如:console.log......
  • 《ASP.NET Core技术内幕与项目实战》精简集-高级组件4.4:消息推送SignalR
    本节内容,部分为补充内容。主要NuGet包:Microsoft.AspNetCore.SignalR.Client(BlazorWASM的SignalR客户端)Microsoft.AspNetCore.SignalR.StackExchangeRedis(使用Redis部署......
  • JavaScript中的箭头函数
    前言本文可以让你了解所有有关JavaScript箭头函数的信息。我们将告诉你如何使用ES6的箭头语法,以及在代码中使用箭头函数时需要注意的一些常见错误。你会看到很多例子来说......
  • Go函数选项模式
    typeUserstruct{IdintNamestring}typeoptionfunc(*User)func(u*User)Option(opts...option){for_,opt:=rangeopts{opt(u......