Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS中作用域和原型链 #4

Open
itboos opened this issue Jan 25, 2018 · 1 comment
Open

JS中作用域和原型链 #4

itboos opened this issue Jan 25, 2018 · 1 comment

Comments

@itboos
Copy link
Owner

itboos commented Jan 25, 2018

##JS中作用域和原型链
2017-07-04

####1.作用域:

   var a =1;
   function fn1(a){
    var a =2;
    console.log(a);
   }
   console.log(a);
   fn1(3);

    //变形1"
    // var a =1;
    // function fn1(a){
    //  console.log(a);
    //  var a =2;
    //  console.log(a);
    // }
    // console.log(a);
    // fn1(3);

    //变形3:
    // var a =1;
    // function fn1(a){
    //  console.log(a);
    //  a=4;
    //  console.log(a);
    // }
    // console.log(a);
    // fn1(3);
    // console.log(a);
    

    //放在不同的script标签里
    var o2='the second script';
    console.log(o);

概念:
作用域:
首先我们需要了解的是作用域做什么的?当JavaScript引擎在某一作用域中遇见变量和函数的时候,需要能够明确变量和函数所对应的值是什么,所以就需要作用域来对变量和函数进行查找,并且还需要确定当前代码是否对该变量具有访问权限。也就是说作用域主要有以下的任务:

收集并维护所有声明的标识符(变量和函数)
依照特定的规则对标识符进行查找
确定当前的代码对标识符的访问权限

来个例子:

function foo(b) {
    console.log( b ); 
}

foo( 3 );

执行上述代码流程如下:

查询标识符foo,得到变量后执行该变量
查询标识符b,得到变量后对其赋值为3
查询标识符console,得到变量后准备执行属性log
查询标识符b,得到变量后,作为参数传入console.log执行

作用域链;
代码可以访问当前的作用域的变量,对于嵌套的父级作用域中的变量也可以访问。我们知道JavaScript在ES5中是没有块级作用域的,只有函数可以创建块作用域。

function Outer(){
    var outer = 'outer';
    Inner();
    function Inner(){
        var inner = 'inner';
        console.log(outer,inner) // outer inner
    }
}

``当引擎执行到函数Inner内部的时候,不仅可以访问当前作用域而且可以访问到Outer的作用域,从而可以访问到标识符outer。因此我们发现当多个作用域相互嵌套的时候,就形成了作用域链。词法作用域在查找标识符的时候,优先在本作用域中查找。如果本作用域没有找到标识符,会继续向上一级查找,当抵达最外层的全局作用域仍然没有找到,则会停止对标识符的搜索。`

看个更加复杂一点的例子:

var a=1;
function fn1(){
    console.log(a);  //第一个a 输出什么
    var a=2;
    function fn1_1(){
        var a=3;
        console.log(a); //第二个a 输出什么
    }
    console.log(a); //第三个a输出什么
    fn1_1();
}
fn1();

关于作用域的总结:
词法作用域在查找标识符的时候,优先在本作用域中查找。如果本作用域没有找到标识符,会继续向上一级查找,当抵达最外层的全局作用域仍然没有找到,则会停止对标识符的搜索。

###2. 原型链

首先来看Number函数: Number()
原始类型的值主要是字符串、布尔值、undefined和null,它们都能被Number转成数值或NaN。

(1).普通值转换为
// 数值:转换后还是原来的值
Number(324) // 324

// 字符串:如果可以被解析为数值,则转换为相应的数值
Number('324') // 324

// 字符串:如果不可以被解析为数值,返回NaN
Number('324abc') // NaN

// 空字符串转为0
Number('') // 0

// 布尔值:true 转成1,false 转成0
Number(true) // 1
Number(false) // 0

// undefined:转成 NaN
Number(undefined) // NaN

// null:转成0
Number(null) // 0
思考: 
parseInt('42 cats') 
Number('42 cats') 

####2)对象的转换规则
规则是: Number方法的参数是对象时,将返回NaN,除非是包含单个数值的数组。

Number({a: 1}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5



思考:
Object.prototype.toString=function(){
    console.log('调用Object.ptototype.valueOf....');

Object.prototype.valueOf=function(){
    console.log('调用Object.ptototype.valueOf....');
    return 998;
}
var obj={
    msg: 'this is a test',
    valueOf: function(){
        return 2;
        //return '123';
        //return undefined;
        //return null;
        //return 'abc';
        //return '';
        //return {}; 返回对象
    }
};

x=Number(obj);
这里会输出什么?

Number背后对于对象的转换比较复杂:
1.调用对象自身的valueOf方法。如果返回原始类型的值,则直接对该值使用Number函数,不再进行后续步骤。

2.如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法。如果toString方法返回原始类型的值,则对该值使用Number函数,不再进行后续步骤。

3.如果toString方法返回的是对象,就报错。
所以:

var obj = {x: 1};
Number(obj) // NaN
// 等同于
if (typeof obj.valueOf() === 'object') {
  Number(obj.toString());
} else {
  Number(obj.valueOf());
}
Number函数将obj对象转为数值。背后发生的操作,首先调用obj.valueOf方法, 结果返回对象本身;于是,继续调用obj.toString方法,这时返回字符串[object Object],对这个字符串使用Number函数,得到NaN

除此之外:
valueOf和toString方法,都是可以自定义的。

Number({
  valueOf: function () {
    return 2;
  }
})


Number({
  toString: function () {
    return 3;
  }
})


Number({
  valueOf: function () {
    return 2;
  },
  toString: function () {
    return 3;
  }
})

正式进入原型链:

    例子1:
    function Person(){
     }
    Object.prototype.sayHi=function(){
       console.log("我是Object原型对象的sayHi方法!");
    };
     Person.prototype.name="LXY";
     Person.prototype.age=22;
     Person.prototype.sayHi=function(){
       console.log(this.name+","+this.age+",");
     };
    var p1=new Person();
    var p2=new Person();
    console.log(p1.name);//'LYX'
     
    p1.sayHi();//'LXY,22'
    p2.sayHi();//'LXY,22'
    console.log(p1.sayHi===p2.sayHi);//true
    hasOwnProperty,isPrototypeOf, getPrototypeOf这三个方法:
        //测试某个对象是否是某个对象的原型
        console.log(Person.prototype.isPrototypeOf(p1)); //true
        console.log(Person.prototype.isPrototypeOf(p2));//true
         
        //ES6新增的一个方法:Object.getPrototypeOf() //返回某个对象的原型对象
         
        console.log(Object.getPrototypeOf(p1)); 
        console.log(Object.getPrototypeOf(p2));
         
        console.log( Object.getPrototypeOf(p2).constructor);//function Person(){ }
        console.log( Object.getPrototypeOf(p2).name);//"LXY"
        console.log( Object.getPrototypeOf(p2).age);//22
        //这两个方法都是返回Person { name="LXY", age=22, sayHi=function()}


    //使用hasOwnProperty()方法可以检测一个属性是否存在于对象本身,还是对象的原型中
         function Person(){
         }
         Person.prototype.name="人类";
         Person.prototype.age=150;
         Person.prototype.sayHi=function(){
           console.log(this.name+","+this.age+",");
         };
         var p1=new Person();
         var p2=new Person();
         p1.name="WFD";
      
关于原型链的图:

         //使用hasOwnProperty()方法可以检测一个属性是否存在于对象本身,还是对象的原型中
         console.log(p1.hasOwnProperty("name")); //true p1对象自己有name属性
         console.log(p2.hasOwnProperty("name")); //false p2对象自己m没有name属性,访问得到的是原型对象中的name属性
     
         delete p1.name; //删除p1对象的name属性
         console.log(p1.hasOwnProperty("name"));//false  
         //============================================
         /*
              使用hasOwnProperty()方法可以检测一个属性是否存在于对象本身,还是对象的原型中
            单独使用in运算符:通过对象,可以访问给定的属性时就返回true,无论该属性是在对象中还是原型对象中
         */
         console.log("name" in p1); //只要name属性可以访问到,就返回true
         组合两种方法,可以判断某个属性是否存在于原型对象中
         function hasPrototypeProperty(object,name){
            return !object.hasOwnProperty(name)&& (name in object);
            //第一个返回false,第二个返回true,则可以说明属性存在于原型对象中
         }
     ```    
         //==============================例子:
         var p1=new Person();
         var p2=new Person();
         p1.name="WFD";
         
         function hasPrototypeProperty(object,name){
          return !object.hasOwnProperty(name)&& (name in object);
          //第一个返回false,第二个返回true,则可以说明属性存在于原型对象中
         }
         var res1=hasPrototypeProperty(p1,'name');
         var res2=hasPrototypeProperty(p2,'name');
         console.log(res1);//false
         console.log(res2);//true


例子2:
//2.原型链查找,每一个访问对象的一个属性时就有一次原型链查找过程
   function Person(){
 
   }
   Person.prototype.name="LXY";
   Person.prototype.age=22;
   Person.prototype.sayHi=function(){
     console.log(this.name+","+this.age+",");
   };
 
   var p1=new Person();
   var p2=new Person();
   p1.name="WFD";
   
   console.log(p1.name);// 'WFD' 来自实例对象p1本身
   console.log(p2.name);// 'LXY'  来自原型对象
   p1.name=null;
   console.log(p1.name);//null
  //删除实例的name属性,从而又可以访问到原型对象的name属性
  delete p1.name;
  console.log(p1.name); // 'LXY'

对于有基于类的语言经验的开发人员来说, JavaScript 有点令人困惑 (如Java或C ++) ,因为它是动态的,并且本身不提供一个类实现.。(在ES2015/ES6中引入了class关键字,但只是语法糖,JavaScript 仍然是基于原型的)。

当谈到继承时,Javascript 只有一种结构:对象。每个对象都有一个私有属性(称为是[[Prototype]]), 它持有一个连接到另一个称为其 prototype 对象的链接。该原型对象具有一个自己的原型,等等,直到达到一个对象的 prototype 为 null。

根据定义,null 没有 prototype,并作为这个 prototype chain 中的最后一个环节。

Object是Function,Array,String,RegExp 的父类,Object是上帝类

 Object.prototype.sayHi=function(){
   console.log("Object原型对象里的sayHi!");
 };

 Function.prototype.sayHi=function(){
   console.log("Function原型对象里的sayHi!");
 };
 Array.prototype.sayHi=function(){
   console.log("Array原型对象里的sayHi!");
 };
String.prototype.sayHi=function(){
  console.log("String原型对象里的sayHi");
};
function Person(name){
   this.name=name;
   this.sayHi=function(){
     console.log("Person对象里的sayHi!");
   };
}
Person.prototype.sayHi=function(){
   console.log("Person原型对象里的sayHi!");
};

var s1="abc",
        fn1=function(){},
        arr=[],
        o1={},
        p1=new Person();
   
   arr.sayHi();//Array原型对象里的sayHi!
   s1.sayHi();//String原型对象里的sayHi!
   fn1.sayHi();//Function原型对象里的sayHi!
   o1.sayHi();//Object原型对象里的sayHi!

   
   p1.sayHi();//Person对象里的sayHi!  Person对象里的sayHi! 首先在Person对象p1上找到了  // p1依次删除 sayHi方法,再看看输出





   执行上面代码时,首先会在对象实例p1中查找属性sayHi方法,我们发现实例中不存在sayHi属性。然后我们转到p1内部指针[[Prototype]]指向的Person原型对象去寻找sayHi属性,结果是仍然不存在。这找不到我们就放弃了?开玩笑,我们这么有毅力。我们会再接着到p1原型对象的内部指针[[Prototype]]指向的Object原型对象中查找,这次我们发现其中确实存在sayHi属性,然后我们执行sayHi方法。发现了没有,这一连串的原型形成了一条链,这就是原型链。
     
  总结: 
     当我们访问对象的某个属性或者方法时,我们首先会在当前对象本身上找,如果找到,就使用这个值,整个查找过程结束,如果未找到,就会在对象的原型链中依次查找,如果在当前的原型中已经查找到所需要的属性,那么就会停止搜索,否则会一直向后查找原型链,直到原型链的结尾(这一点有点类似于作用域链),如果直到原型链结尾仍未找到,那么该属性就是undefined

作用域链和原型链的比较:
讲完了作用域链和原型链,我们可以比较一下。作用域链的作用主要用于查找标识符,当作用域需要查询变量的时候会沿着作用域链依次查找,如果找到标识符就会停止搜索,否则将会沿着作用域链依次向后查找,直到作用域链的结尾。而原型链是用于查找引用类型的属性,查找属性会沿着原型链依次进行,如果找到该属性会停止搜索并做相应的操作,否则将会沿着原型链依次查找直到结尾。

拓展__proto__ 和prototype:

huangtengfei/blog#11

参考链接:
http://dmitrysoshnikov.com/ecmascript/javascript-the-core/ JavaScript. The core.
http://www.ecma-international.org/ecma-262/5.1/#sec-4.3.5 ESCMA5官网
http://dmitrysoshnikov.com/tag/ecma-262-3/
creeperyang/blog#9
http://www.th7.cn/web/js/201503/88712.shtml
huangtengfei/blog#11

@itboos
Copy link
Owner Author

itboos commented Aug 20, 2018

自己写的文章1
详解JS中的原型链-2
详解JS中的原型链-1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant