zoukankan      html  css  js  c++  java
  • JS原型,作用域,this,闭包

    一,原型链

    1. JS是通过原型机制来实现面向对象的继承的,那JS是不是面向对象的语言呢?广义上说是的,但他并没有像C#、Java语言那么容易实现多态。

    2. 每一个函数都有一个prototype,我们可以把那些不变(共用)的属性和方法,直接定义在prototype对象属性上。

    3. JS中的普通函数(箭头函数除外)都可以作为构造器生成对象,函数也是一种特殊的对象,函数默认会有一个prototype属性,它的指向等于由这个函数生成对象的原型,假如有Person函数和它的对象p1,则 (Person.prototype === p1.__proto__)

    function Person(){
    }
    
    var p1 = new Person();
    console.log(p1.__proto__
    === Person.prototype); // true, 注意 __proto__是一个隐藏属性,没有被标准化,兼容性不好,生产中避免使用 console.log(Object.getPrototypeOf(p1) === Person.prototype); // true

      console.log(Reflect.getPrototypeOf(p1) === Person.prototype); // true, ES6+后推荐写法

     console.log(Person === Person.prototype.constructor); // true 原型上有一个构造器对象 指向函数。
     
      console.log(p1.constructor=== Person.prototype.constructor); // true

      console.log(p1.constructor=== Person); // true

     console.log(p1.__proto__ === p1.constructor.prototype) // true

    console.log(Person.__proto__ === Function.prototype); //true, pay attention to this, 所有函数的__proto__都指向Function.prototype
    console.log(Object.getPrototypeOf(String) === Function.prototype) // true
    console.log(Object.getPrototypeOf(Object) === Function.prototype) // true

    Note

    function Person(name) { this.name = name } 
    Person.prototype = { getName: function() {} } // 这里修改了原型指向
    var p = new Person('jack') console.log(p.__proto__ === Person.prototype) // true console.log(p.__proto__ === p.constructor.prototype) // false,这里不相等了,因为p的构造函数还等于之前那个空对象。

    给Person.prototype赋值的是一个对象直接量{getName: function(){}},使用对象直接量方式定义的对象其构造器(constructor)指向的是根构造器Object,Object.prototype是一个空对象{},{}自然与{getName: function(){}}不等。
        var obj = {}
         此处等价于 var obj = new Object()
        console.log(obj.__proto__ === Object.prototype)//true  
    
         var obj = []
       // 等价于 var arr= new Array();
        console.log(obj.__proto__ === Array.prototype)//true

    4. 所有函数的还有一个proto属性,它执行Function.prototype

    Boolean.__proto__ === Function.prototype // true Boolean.constructor == Function //true 
    String.__proto__ === Function.prototype // true 
    String.constructor == Function //true 
    // 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身 
    Object.__proto__ === Function.prototype // true Object.constructor == Function // true 
    // 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身 Function.__proto__ === Function.prototype // true 
    Function.constructor == Function //true 
    Array.__proto__ === Function.prototype // true 
    Array.constructor == Function //true 
    RegExp.__proto__ === Function.prototype // true 
    RegExp.constructor == Function //true 
    Error.__proto__ === Function.prototype // true 
    Error.constructor == Function //true 
    Date.__proto__ === Function.prototype // true 
    Date.constructor == Function //true

    some day updates: the article is better to understand to know the prototype of JS, https://juejin.cn/post/6844903837623386126

    二、作用域

    JS中有3种主要的作用域,全局作用域,函数作用域,ES6中新增的块作用域  {},for(), while(), swith(), if()这几种表达式都会生成块作用域。

    JS执行过程分编译和解释执行两个阶段(源码 -> 字节码 -> 二进制,AST协助ES6 -> ES5, JIT会缓存一些多次调用而产生的优化过后的二进制码 ),编译过程中会有变量提升(var 变量提升(此时等于undefined,可访问),let和const提升后不能访问(暂时性死区), 函数定义也会提升),后定义的会覆盖先定义的。

    函数执行上线文中查询对象作用域范围大致如下

     So,可以分析下面的代码

    function bar() {
        var myName = "roy"
        let test1 = 100
        if (1) {
            let myName = "Chrome浏览器"
            console.log(test)
        }
    }
    function foo() {
        var myName = "foo"
        let test = 2
        {
            let test = 3
            bar()
        }
    }
    var myName = "world"
    let myAge = 10
    let test = 1
    foo() 
    // 分析段代码,最终输出的是 1, 它是外出window定义的test值。

    三、 this指针

    1. 在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window。

    function foo(){
      console.log(this)
    }
    foo() // 非严格模式下输出window

    2. 通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身。

    var myObj = {
      name : "roy", 
      showThis: function(){
        console.log(this)
      }
    }
    myObj.showThis() //输出 {name: "roy", showThis: ƒ}

    //特别注意下面这样,this又指向window去了

      var temp = myObj.showThis;
     temp(); //这会儿输出Window了。

    let bar = {  myName : "roy",  test1 : 1}
    function foo(){
      this.myName = "Tim"
    }
    foo.call(bar), //还可以使用apply
    console.log(bar) // 输出Tim.
    let bar = { 
     myName : "roy",  
     test1 : 1
    }
    
    function foo(){  this.myName = "jerry"}
    var k=foo.bind(bar);
    k();
    console.log(bar) // 输出jerry,所以bind方法会始终固定下this的指向。
    function CreateObj(){
      this.name = "Roy"
    }
    var myObj = new CreateObj()
    
    //本质上是下面这样
    
    var tempObj = {}
    CreateObj.call(tempObj)
    return tempObj

    this 的缺陷

    a. 嵌套函数中的 this 不会从外层函数中继承

    var myObj = {
      name : "Roy", 
      showThis: function(){
        console.log(this)  //这里输出myObject这个对象
        function bar(){
            console.log(this) // 这里却输出Window对象
        }
        bar()
      }
    }
    myObj.showThis()    

    how to fix it?

    • 第一种是把 this 保存为一个 self 变量,再利用变量的作用域机制传递给嵌套函数。
    • 第二种是继续使用 this,但是要把嵌套函数改为箭头函数,因为箭头函数没有自己的执行上下文,所以它会继承调用函数中的 this。
    var myObj = {
      name : "Roy", 
      showThis: function(){
        console.log(this)
        var bar =()=> {
          this.name = "Jerry"
          console.log(this)
        }
        bar()
      }
    }
    myObj.showThis()  //Roy
    console.log(myObj.name) //Jerry
    console.log(window.name) //这里也会输出Jerry,诡异。

    this的坑,下面输出的实际上没有this的引用,小心掉坑。

    var bar = {
        myName:"Roy",
        printName: function () {
            console.log(myName)
        }    
    }
    function foo() {
        let myName = "Jerry"
        return bar.printName
    }
    
    let myName = "Jenifer"
    let printName = foo()
    printName()  // Jenifer
    bar.printName()  //Jenifer

    第四 闭包

    函数内部的函数引用了外部函数的局部变量,且返回这个内部函数。外部函数执行完了后,返回的函数还包含外部函数中定义的变量,这样就会创建一个外部函数的闭包。

    所以这些变量都是保存在堆上的,so,通常如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存。

    作用一,封装对象的访问和写入,提供了一种外部可以读写函数内部的变量的机制,类似Java中的get,set访问器。

    function foo() {
        var myName = "Roy"
        let test1 = 1
        const test2 = 2
        var innerBar = {
            getName:function(){
                console.log(test1)
                return myName
            },
            setName:function(newName){
                myName = newName  // 这里的myName的作用域链是,先setName方法内部看是否有myName有定义?没有就去闭包(foo的背包)中找?这里找到了,如果还是没有找打就要去全局执行上下文中找了。
            }
        }
        return innerBar
    }
    var bar = foo()
    bar.setName("Jenifer")
    bar.getName()
    console.log(bar.getName()) // 这里输出Jenifer


    -------------------我是简陋的分割线--------------

    function Person(){   
        var name = "default";      
          
        return {   
           getName : function(){   
               return name;   
           },   
           setName : function(newName){   
               name = newName;   
           }   
        }   
    };   

    var john = Person();   
    print(john.getName());   
    john.setName("john");   
    print(john.getName());

    另一个作用是就是让这些变量的值始终保持在内存中,做缓存。

    //这里会形成一个foo的闭包closure,且a变量会保存在堆上的
    function foo() {
        var a = 0
        return function inner() {
            return a++  
        }
    }
  • 相关阅读:
    使用 connect http proxy 绕过 ssh 防火墙限制
    Docker 容器与宿主机网段冲突导致网络无法 ping 通的解决方案
    Mac 下 Chrome 浏览器 ERR_NETWORK_CHANGED 报错解决方案
    SCSS & SASS Color 颜色函数用法
    eggjs 框架代理调试 SELF_SIGNED_CERT_IN_CHAIN 报错解决方案
    【转】谷歌安装Axure浏览器插件
    js 图片转base64并压缩
    ajax请求
    postman如何重置账号的密码
    解决url中&times会被转成×的问题
  • 原文地址:https://www.cnblogs.com/roy1/p/13745551.html
Copyright © 2011-2022 走看看