Skip to content

npm version

Published: at 12:00 AM


最近再次拜读javascript高级程序设计函数部分,发现一个问题。所以这里拿出来分享一下。书中提到下面两个表达式输出结果应该分别为20和TypeError,因为函数声明会涉及到函数声明提升的影响,使其在执行任何代码之前可用。函数表达式必须等到解析器执行到其所在的代码行才开始执行。

    console.log(params(10, 10));
    function params(num1, num2){
     return num1+num2;
    }
    //20
    console.log(params(10, 10));
    var params= function(num1, num2){
     return num1+num2;
    };
    //TypeError: params is not a function.


于是在浏览器中试验得到了一个吃惊的结果。
上面的情况在safari中完全满足,Version 9.1.1;
如果把console.log,改成alert, Safari中的结果又正常了。

    alert(params(10, 10));
    var params= function(num1, num2){
     return num1+num2;
    };
    //20


但是在chrome和firefox中都会输出20,chrome Version 50.0.2661.94; firefox Version 46.0.1;
构造函数相关:

    function Person(name, age, sex){
     this.name= name;
     this.age= age;
     this.sex= sex;
     this.sayName= function(){
       console.log(this.name);
     };
    }
    var person1= Person("fred", "18", "man");  //直接调用函数把属性方法添加到了window对象上。
    person1.sayName() //Uncaught TypeError: Cannot read property 'sayName' of undefined(…)

    function Person(name, age, sex){
     this.name= name;
     this.age= age;
     this.sex= sex;
     this.sayName= function(){
       console.log(this.name);
     };
    }
    var person1= Person("fred", "18", "man");
    window.sayName();  //fred

    function Person(name, age, sex){
     this.name= name;
     this.age= age;
     this.sex= sex;
     this.sayName= function(){
       console.log(this.name);
     };
    }
    var o={};
    var person1= Person.call(o,"fred", "18", "man");
    o.sayName()  //fred  通过call方法定义一个对象作为接受者。

    function Person(name, age, sex){
     this.name= name;
     this.age= age;
     this.sex= sex;
     this.sayName= function(){
       console.log(this.name);
     };
    }
    var person1= new Person("fred", "18", "man");
    var person2= new Person("helen", "18", "female");
    person1.sayName === person2.sayName //false   两个方法不是同一个构造函数的实例。


改造方法是把函数的定义移到构造函数的外面。把sayName属性设置成等于全局的sayName函数,这样一来,sayName包含的是指向函数的指针,因此person1和person2共享了在全局作用域中定义的同一个sayName函数,这样做确实解决了,两个函数做一件事情的问题,但是新的问题是,在全局作用域中定义的函数,仅仅用到了这里,有点浪费啊,而且定义很多方法的时候意味着定义很多全局变量,毫无封装性可言。

    function Person(name, age, sex){
     this.name= name;
     this.age= age;
     this.sex= sex;
     this.sayName= sayName;
    }
    function sayName(){
       console.log(this.name);
     };
    var person1= new Person("fred", "18", "man");
    //实例化实质是新建一个对象,把构造函数的属性方法以及作用域复制到实例函数中,再返回实例函数。
    var person2= new Person("helen", "18", "female");
    person1.sayName === person2.sayName  //true


再次改造的方法是通过在原型上定义方法。

function Person(){}; Person.prototype.name= “fred”; Person.prototype.age= “28”; Person.prototype.sex= “male”; Person.prototype.sayName=function(){ return this.name; } var person1= new Person(); var person2= new Person(); person1 === person2;


遍历对象属性各个方法的区别。 (1) for … in; Object.getOwnPropertyNames(); object.hasOwnProperty(); Object.keys()

    function Person(){};
    Person.prototype.name= "fred";
    Person.prototype.age= "28";
    Person.prototype.sex= "male";
    Person.prototype.sayName=function(){
      return this.name;
    }
    var person1= new Person();
    person1.color= "yellow";
    person1.look= function(){
      return "what are you look";
    }
    var array1= [];
    for (var prop in Person.prototype){
      array1.push(prop);
    }
    console.log(array1);  //
    ["name", "age", "sex", "sayName"]  for ... in遍历对象的可枚举属性。
    console.log(person1.hasOwnProperty("name")); //false
    console.log(Person.prototype.hasOwnProperty("constructor"));
    //true 获得对象上可枚举和不可枚举的所有实例属性。
    console.log(Object.getOwnPropertyNames(person1)); //["color", "look"]
    console.log(Object.getOwnPropertyNames(Person.prototype)); //["constructor", "name", "age", "sex", "sayName"]  获得对象上可枚举和不可枚举的所有实例属性。
    console.log(Object.keys(Person.prototype));
    // ["name", "age", "sex", "sayName"]  获取对象上的所有可枚举属性。

(2) in, hasOwnProperty在实例中的应用; in可以访问实例上和原型上定义的属性;hasOwnProperty只能访问实例上的属性。

    function Person(){};
    Person.prototype.name= "fred";
    Person.prototype.age= "28";
    Person.prototype.sex= "male";
    Person.prototype.sayName=function(){
      return this.name;
    }
    var person1= new Person();
    person1.color= "red";
    function hasPrototypeProperty(obj, name){
       return !obj.hasOwnProperty(name) && (name in obj);
    }

    console.log(hasPrototypeProperty(person1, "color")); //false;
    console.log(hasPrototypeProperty(person1, "name")); //true;


论prototype对象字面量写法,和赋值写法的差异。

    function Person(){};
    Person.prototype= {
      name: "fred",
      age: "18",
      sex: "male",
      sayName: function(){
        return this.name;
      }
    };
    var person1= new Person();
    person1.color= "red";
    function hasPrototypeProperty(obj, name){
       return !obj.hasOwnProperty(name) && (name in obj);
    }
    console.log(person1.constructor === Person);  //false ,继承了原型的constructor属性,原型的constructor属性指向了Object,而不是Person。
    console.log(person1.constructor === Object);  //true
    console.log(person1.prototype); //undefined; 因为重写原型切断了原型和原来创建的实例的联系。
    console.log(Object.getOwnPropertyNames(Person.prototype));
    //  ["name", "age", "sex", "sayName"]
    console.log(Person.prototype.constructor === Person);  //false 因为对象字面量相当于把原型对象重写了,因此constructor属性也就变成了新对象的constructor属性,指向Object。

    function Person(){};
    Person.prototype.name= "fred";
    Person.prototype.age= "28";
    Person.prototype.sex= "male";
    Person.prototype.sayName=function(){
      return this.name;
    }
    var person1= new Person();
    console.log(person1.constructor === Person);
    console.log(person1.prototype); //undefined;  ???????
    console.log(Object.getOwnPropertyNames(Person.prototype));
    //["constructor", "name", "age", "sex", "sayName"]
    console.log(Person.prototype.constructor === Person); //true;


原型的问题:原型中的属性被所有的实例共享,这种共享对于函数非常合适,对于包含基本值的属性,通过覆盖原型属性也可以实现,但是对于应用类型的例如数组的话,该一个实例的属性,原型也跟着变化,紧接着所有实例都变化。

    function Person(){};
    Person.prototype= {
      constructor: Person,
      name: ["fred", "paul"],
      age: "18",
      sex: "male",
      sayName: function(){
        return this.name;
      }
    };
    var person1= new Person();
    var person2= new Person();
    person1.name.push("helen");
    console.log(person1.name);  //["fred", "paul", "helen"]
    console.log(person1.name === person2.name);  //true


改善1:组合使用构造函数模式和原型模式

    function Person(age, sex){
      this.name= ["fred", "paul"],
      this.age= age,
      this.sex= sex
    };
    Person.prototype= {
      constructor: Person,
      sayName: function(){
        return this.name;
      }
    };
    var person1= new Person("18","male");
    var person2= new Person("20","female");
    person1.name.push("helen");
    console.log(person1.name); //["fred", "paul", "helen"]
    console.log(person2.name); //["fred", "paul"]
    console.log(person1.sayName());  //["fred", "paul", "helen"]
    console.log(person2.sayName());  //["fred", "paul"]
    console.log(person1.sayName === person2.sayName);  //true;


改善2:动态原型创建法

    function Person(age, sex){
      this.name= ["fred", "paul"],
      this.age= age,
      this.sex= sex
      if (typeof this.sayName != "function"){
        Person.prototype.sayName= function(){
          console.log(this.name);
        }
      }
    };
    var person1= new Person("18","male");
    var person2= new Person("20","female");
    person1.name.push("helen");
    person1.sayName();  //["fred", "paul", "helen"]
    person2.sayName();  //["fred", "paul"]


改善3:寄生构造模式

    function SpecialArray(){
      var values= new Array();
      values.push.apply(values, arguments);
      values.toPipeValue= function(){
        return this.join("|");
      }
      return values;
    }
    var array1= new SpecialArray("14", "29", "40");
    console.log(array1.toPipeValue());  //14|29|40