组团学

JavaScript-高级属性

阅读 (355)

一、constructor属性

说明:每个对象都有该属性,引用对象构造器函数

  • 内置对象

    var str = "sunck is a good man"; var arr = [1,2,3,4,5]; var date = new Date(); console.log(str.constructor); console.log(arr.constructor); console.log(date.constructor);
  • 函数对象(函数是一种特殊的数据类型,但是事实还远不止如此,它实际上是一种对象)

    function myFunc(a) { return a; } console.log(myFunc.constructor);
  • 自定义对象

    function Person(name) { this.name = name; } var per = new Person("sunck"); console.log(per.constructor); console.log(Person.constructor);

二、prototype属性

  • 简介

    称为"原型属性",每个函数都有prototype属性,它的初始值是一个"空"对象(原型对象)

原型属性原型对象2782243.png

// 内置对象 var str = "sunck is a good man"; //函数对象 function func(a, b){ return a + b; } //构造函数 function Person(name, age){ this.name = name; this.age = age; } //构造函数创造的对象 var per = new Person("sunck", 18); //字面量定义对象 var car = {color: "red"}; console.log(str.prototype);//undefined console.log(func.prototype);//Object {} console.log(Person.prototype);//Object {} console.log(per.prototype);//undefined console.log(car.prototype);//undefined
  • 早期动态添加属性与方法

    问题:早期动态添加属性与方法的方式只能给某个对象单独添加,对于其它对象没有影响

    function Person(name){ this.name = name; this.run = function(){ console.log("我跑"); }; } var per1 = new Person("sunck"); var per2 = new Person("kaige"); per1.faceValue = 100; per1.eat = function(){ console.log("----eat"); }; console.log(per1); console.log(per2);
  • 通过构造器函数的prototype属性来动态添加属性与方法

    注意:我们可以给这个空对象赋予一些属性和方法,这并不会对函数本身造成什么影响,因为当只有函数作为构造器使用时,这些属性才会起作用

    function Person(name){ this.name = name; this.run = function(){ console.log("我跑"); }; } var per1 = new Person("sunck"); var per2 = new Person("kaige"); Person.prototype.age = 18; Person.prototype.play = function(){ console.log("我玩"); } Person.prototype.say = function(){ console.log(this.name+" is "+this.age+" years old!"); } console.log(Person.prototype);
  • 另外定义对象将其覆盖到之前的原型上来动态添加属性与方法

    注意:当我们重写(覆盖)对象的prototype时,需要重置相应的constructor属性

    function Person(name){ this.name = name; this.run = function(){ console.log("我跑"); }; } var per1 = new Person("sunck"); var per2 = new Person("kaige"); Person.prototype = { age: 18, play: function(){ console.log("我玩"); }, say: function(){ console.log(this.name+" is "+this.age+" years old!"); } } var per3 = new Person("kaishen"); console.log(per3.constructor); console.log(per3.constructor===Person); //修正constructor属性 Person.prototype.constructor = Person; console.log(per3.constructor); console.log(per3.constructor===Person); console.log(Person.prototype);
  • 使用原型对象的属性与方法的原理

    使用原型对象的属性与方法与使用自身的属性方法类似,当我们访问per对象的name(假设构造函数中有)属性时,JS引擎会遍历该对象的所有属性,并查找出name属性,如果找到了就会立即返回其值。当我们访问per对象的age(假设构造函数中没有)属性时,JS引擎依然会遍历该对象的所有属性,但是这一回它找不到一个叫age的属性了。接下来,JS引擎就会去查找用于创建该对象的构造器函数的原型对象(也就是构造函数的prototype属性所引用的对象),如果找到了该属性,就立即使用该属性

  • 通过构造器函数的prototype属性来增加后使用原型对象的属性与方法

    注意:对于原型来说,最重要的一点是要理解的它的"实时性"。由于JS中几乎所有的对象都是通过传引用来传递的,因此我们所创建的每个新的实体中并没有一份属于自己原型的副本。这也就意味着我们可以随时修改prototype属性,并且由同一构造器创建的所有对象的prototype属性也都会同时改变,甚至还会影响在修改之前就已经创建的那些对象

    function Person(name){ this.name = name; this.run = function(){ console.log("我跑"); }; } var per = new Person("sunck"); Person.prototype.age = 18; Person.prototype.play = function(){ console.log("我玩"); } Person.prototype.say = function(){ console.log(this.name+" is "+this.age+" years old!"); } console.log("----原有属性与方法----"); console.log(per.name); per.run(); console.log("----原型属性与方法----"); console.log(per.age); per.play(); per.say(); console.log(per.constructor.prototype.age); // console.log(per.__proto__.age);//后面讲解
  • 另外定义对象将其覆盖到之前的原型上之使用原型对象的属性与方法

    注意:此种方式只有在覆盖原型对象之后创建的对象才可以直接访问原型对象的属性与方法

    function Person(name){ this.name = name; this.run = function(){ console.log("我跑"); }; } var per1 = new Person("sunck"); Person.prototype = { age: 18, play: function(){ console.log("我玩"); }, say: function(){ console.log(this.name+" is "+this.age+" years old!"); } } //修正constructor属性 Person.prototype.constructor = Person; console.log("----per1----"); console.log(per1.name); per1.run(); // console.log(per1.age); // per1.play(); // per1.say(); console.log(per1.constructor.prototype.age); console.log("----per2----"); var per2 = new Person("kaige"); console.log(per2.age); per2.play(); per2.say(); console.log(per2.constructor.prototype.age); // console.log(per2.__proto__.age);//后面讲解
  • 利用自身属性重写原型属性

    说明:当自身属性与原型属性相同时,使用的是自身属性。当我们把自身属性删除时,原型属性才会浮出水面

    var base = { height: 175, weight: 65 }; function Person(name, age, height) { this.name = name; this.age = age; //base会成为Person构造器函数的原型对象 //在Person中有一个与原型中相同的属性 this.height = height; } Person.prototype = base; Person.prototype.constructor = Person; //创建一个对象 var per = new Person("sunck", 18, 180); console.log(per.height);//根据属性使用原理会使用自身属性而非原型对象中的属性 delete per.height; console.log(per.height);
    • hasOwnProperty()方法

      作用:判断一个属性是自身属性还是原型对象中的属性

      返回值:自身属性返回true,否则返回false

      console.log(per.hasOwnProperty("name")); console.log(per.hasOwnProperty("weight"));
    • isPrototypeOf()方法

      作用:判断一个对象是否是另一个对象的原型对象

      返回值:是返回true,否则返回false

      console.log(base.isPrototypeOf(per)); console.log(new Array(5).isPrototypeOf(per));

三、for-in枚举属性

function Person(name, age) { this.name = name; this.age = age; this.say = function(){ return 'I am ' + name + ", I am " + age + " years old!"; }; } Person.prototype.height = 175; Person.prototype.weight = 80; Person.prototype.getInfo = function() { return "height: " + this.height + " weight: " + this.weight; }; var per = new Person("sunck", 18); console.log(per); for (var i in per) { console.log(i); }
  • 并不是所有的属性都会在for-in循环中显示,例如(数组的)的length属性和constructor属性就不会被显示。那些会显示的属性被称为是可枚举的(可以使用propertyIsEnumerable()方法判断是否可枚举)。对于內建属性和方法来说,它们大部分是不可枚举的

    var arr = [1,2,3,4,5]; console.log(arr); for (var i in arr){ console.log(i); } console.log(arr.propertyIsEnumerable(0)); console.log(arr.propertyIsEnumerable("length"));
  • 原型链中的各个原型属性也会被显示出来,当然前提是他们是可枚举的。但是对于所有的原型对象中的属性,propertyIsEnumerable()方法都会返回false,包括哪些在for-in循环中显示的属性

    console.log(per.propertyIsEnumerable("name"));//true console.log(per.propertyIsEnumerable("height"));//false console.log(per.propertyIsEnumerable("constructor"));//false
  • 只打印自身属性

    for (var j in per) { if (per.hasOwnProperty(j)) { console.log(j); } }

四、神秘的__proto__链接

注意:每个对象都有一个__proto__属性,使用对象中不存在的某个属性时得去原型对象中查找,就好像其中有一个神秘的链接或者通道指向了相关的原型独享。js环境中,对象中确实存在一个指向相关原型对象的链接,这个神秘的链接就是__proto__属性

var base = { height: 175, weight: 65 }; function Person(name, age) { this.name = name; this.age = age; } Person.prototype = base; Person.prototype.constructor = Person; var per = new Person("sunck", 18); console.log(per.height) console.log(per.constructor.prototype.height); console.log(per.__proto__.height);

注意:以后请避免在实际中使用__proto__属性,因为该属性在IE之类的浏览器中不存在,因此脚本就不能跨平台了

区分__proto__prototype

  • 两者并不是等价的
  • __proto__实际上是某个实例对象的属性
  • prototype是属于构造器函数的属性

五、图解分析属性关系

图解属性关系.png

六、构造器函数的创建技巧

凡是使用构造器函数创建的对象的__proto__属性都引用同一个原型对象,所以以后所用对象共有的属性且属性值相同一般不会进行修改的放入原型对象中定义,公用方法一般放入原型对象中定义,每个对象需要不同的属性值的属性放入构造器函数中定义

var base = { description: "人类", getDescription: function(){ console.log(this.description); } }; function Person(name, age) { this.name = name; this.age = age; } Person.prototype = base; Person.prototype.constructor = Person; var per1 = new Person("sunck", 18); var per1 = new Person("kaige", 18);

七、call()方法

说明:每个函数都有call、apply、bind这三个方法

注意:一般来说,this总是指向调用某个方法的对象,但是使用call、apply、bind方法时,就会改变this的指向

作用

  • 可以用他们触发函数,并制定相关的调用参数

    window.color = 'red'; document.color = 'yellow'; var obj = {color: 'blue' }; function changeColor(){ console.log(this.color); } changeColor.call(); //red (默认传递参数) changeColor.call(window); //red changeColor.call(document); //yellow changeColor.call(this); //red changeColor.call(obj); //blue
  • 可以让一个对象去"借用"另一个对象的方法,并为己所用

    var per1 = { name: "sunck", say: function(who1, who2){ console.log("Hi "+who1+" "+who2+" I am "+this.name); } }; per1.say("lilei", "hanmeimei"); var per2 = { name: "kaige" }; // 由于say()函数的对象方法call()时传递了三个参数,对象per2和字符串"lilei"与"hanmeimei"。这样,当say()函数被调用时,其中的this就被自动设置成了per2对象的引用,因此我们看到this.name返回的不再是"sunck",而是"kaige"了 per1.say.call(per2, "lilei", "hanmeimei");

八、apply()方法

工作方式与call类似,唯一不同之处在于参数的传递形式,这里使用的参数是通过一个数组传递

var per1 = { name: "sunck", say: function(who1, who2){ console.log("Hi "+who1+" "+who2+" I am "+this.name); } }; per1.say("lilei", "hanmeimei"); var per2 = { name: "kaige" }; per1.say.apply(per2, ["lilei", "hanmeimei"]);

九、bind()方法

bind和call方法调用形式类似,但是原理完全不同

原理:bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数

var per1 = { name: "sunck", say: function(who1, who2){ console.log("Hi "+who1+" "+who2+" I am "+this.name); } }; var per2 = { name: "kaige" }; var func = per1.say.bind(per2, "lilei", "hanmeimei"); func();
需要 登录 才可以提问哦