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++  
    }
}

 

posted @ 2020-09-28 16:42  lswtianliang  阅读(199)  评论(0编辑  收藏  举报