JS 原型 prototype

JS 里有一个很重要的概念就是原型,不管在日常工作中还是面试中经常会遇到,下面我就来系统地给大家讲解下 JS 里原型的相关知识。

1.是什么

原型是每个函数都有的一个属性,是一个指针,指向一个对象,对象里包含一些属性和方法,由所有实例共享,当然这些属性和方法是根据具体业务情况自己添加的。

2.用处

由1可知,原型存在的目的就是为了让所有实例共享属性和方法,以便节约内存、共享数据。

3.示例

// 定义函数 Person
function Person(){}

// 在函数 Person 的原型上添加共享的属性和方法
Person.prototype.name = "Nicholas";
Person.prototype.sayHello = function(){
  alert("Hello World!");
};

// 定义实例 person1 并调用其 sayName 方法
var person1 = new Person();
console.log(person1.name) // "Nicholas"
person1.sayHello (); // "Hello World!"

// 定义实例 person2 并调用其 sayName 方法
var person2 = new Person();
console.log(person2.name) // "Nicholas"
person2.sayHello (); // "Hello World!"

// 以上两个实例都能取到原型上定义的 name 属性,也都能调用原型上定义的 sayName 方法,实现了属性和方法的共享

4.原型对象的理解

1.函数的 prototype 属性指向原型对象

2.原型对象的 constructor 属性指回函数

3.实例的 [[Propotype]] 属性指向原型对象(某些浏览器可用 __proto__ 访问该属性,其他实现不可访问)

判断实例和原型对象的关系有两个方法:isPrototypeOf() 和 Object.getPrototypeOf()

  • isPrototypeOf() 方法:确定是否是实例的原型对象
alert(Person.prototype.isPrototypeOf(person1));  // true
  • Object.getPrototypeOf() 方法:返回 [[Propotype]] 的值
alert(Object.getPrototypeOf(person1) == Person.prototype); // true
alert(Object.getPrototypeOf(person1).name); // "Nicholas"

4.读取对象属性时,先检查实例本身是否有该属性,没有则检查其原型对象是否有该属性,一旦设置了实例的属性,则只有 delete 后才能访问到原型的相同属性

检查属性是否存在有两种方法:hasOwnProperty() 和 in

  • hasOwnProperty() 方法:检查一个属性是否存在于实例中
function Person(){}
Person.prototype.name = "Nicholas";
var person1 = new Person();
alert(person1.hasOwnProperty("name")); // false
  • in:检查一个属性是否存在,无论实例或原型中
function Person(){}
Person.prototype.name = "Nicholas";
var person1 = new Person();
alert("name" in person1);  // true

5.遍历属性

遍历对象属性有 3 种方法:for-in、Object.keys() 和 Object.getOwnPropertyNames()

  • for-in:遍历所有可访问、可枚举属性名(即使设置了 [[Enumerable]] 为 false 的属性也会被返回,因为根据规定所有开发人员定义的属性都是可枚举的,IE8 及更早版本除外)
function Person(){}

Person.prototype.name = "Nicholas";
var person1 = new Person();
for (var prop in person1){
  alert(prop); // “name”
}
  • Object.keys() 方法:获取所有可枚举的实例属性名
function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
  alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys); // "name,age,job,sayName"
 
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); // "name,age"
  • Object.getOwnPropertyNames() 方法:所有实例属性,无论是否可枚举
// 接上示例
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); // "constructor,name,age,job,sayName"

// constructor 这种不可枚举属性可会被返回 

6.简单原型语法

以上写原型的语法太累赘,可以用一个包含所有属性和方法的对象字面量来重写整个原型对象

function Person(){}

Person.prototype = {
  name : "Nicholas",
  age : 29,
  job: "Software Engineer",
  sayName : function () {
    alert(this.name);
  }
};

注意:此时 constructor 属性不再指向 Person,而是指向 Object 构造函数,通过 constructor 已经无法确定对象的类型了

var friend = new Person();

alert(friend instanceof Object); // true
alert(friend instanceof Person); // true
alert(friend.constructor == Person); // false 
alert(friend.constructor == Object); // true

解决办法:可设置回 Person

Person.prototype = {
  constructor : Person,
  name : "Nicholas",
  age : 29,
  job: "Software Engineer",
  sayName : function () {
    alert(this.name);
  }
};

这种方式会导致 constructor 属性的 [[Enumerable]] 特性被设置为 true(即可枚举),可通过 Object.defineProperty() 的方式设置回来

Object.defineProperty(Person.prototype, "constructor", {
  enumerable: false,
  value: Person
}); 

7.原型动态性

原型动态性体现在:先创建实例,再修改原型,也会反映到实例上

var friend = new Person();
Person.prototype.sayHi = function(){
  alert("hi");
};
friend.sayHi(); // "hi"

但注意:先创建实例,再重写原型,则不会反映到实例上,因为已存在的实例的原型仍是最初的原型

function Person(){}

var friend = new Person();
Person.prototype = {
  constructor: Person,
  name : "Nicholas",
  age : 29,
  job : "Software Engineer",
  sayName : function () {
    alert(this.name);
  }
};

friend.sayName(); // error

8.原生对象原型

所有原生引用类型(Object、Array、String 等)都在其构造函数的原型上定义了方法。 例如,在 Array.prototype 中可找到 sort() 方法,在 String.prototype 中可找到 substring() 方法。

可以像修改自定义对象的原型一样修改原生对象的原型,所以可获取所有默认方法的引用,也可添加新方法。

// 给基本类型 String 添加一个名为 startsWith() 的方法:

String.prototype.startsWith = function (text) {
  return this.indexOf(text) == 0;
};

var msg = "Hello world!";
alert(msg.startsWith("Hello")); // true

注意:这种方式不推荐,因为一是可能会导致命名冲突,二是可能意外地重写原生方法。

9.缺点

使用原型模式构造对象的缺点在于:原型上定义的属性会被实例共享,对于函数类型的属性而言很有用,对于基本值类型的属性也还好(实例上添加一个同名属性就会覆盖原型上的属性),但对于引用类型的属性,一个实例改了也会反映到另一个实例上,这是我们不想要的。

function Person(){}

Person.prototype = {
  constructor: Person,
  name: "Nicholas",
  age: 29,
  job: "Software Engineer",
  friends: ["Shelby", "Court"],
  sayName: function () {
    alert(this.name);
  }
};

var person1 = new Person();
var person2 = new Person();

person1.friends.push("Van");

alert(person1.friends); // "Shelby,Court,Van"
alert(person2.friends); // "Shelby,Court,Van"
alert(person1.friends === person2.friends); // true

解决办法是:组合使用原型模式和构造函数模式,原型模式用于定义方法和共享属性,构造函数模式用于定义实例属性(最大限度地节省了内存,且支持向构造函数传递参数)

function Person(name, age, job){
  this.name = name;
  this.age = age;
  this.job = job;
  this.friends = ["Shelby", "Court"];
}

Person.prototype = {
  constructor: Person,
  sayName: function(){
    alert(this.name);
  }
}
 
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");

alert(person1.friends); // "Shelby,Count,Van"
alert(person2.friends); // "Shelby,Count"
alert(person1.friends === person2.friends); // false
alert(person1.sayName === person2.sayName); // true

此模式是目前使用最广泛、认同度最高的一种创建自定义类型的方法,可以说这是定义引用类型的一种默认模式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值