1.原型
1.1. 抛出问题
let animals = {
name: "animal",
type: "object",
}
let r1 = animals.hasOwnProperty("name")
console.log(r1); //true
但是我们并没有给animals定义方法hasOwnProperty,可它为什么可以调用该方法
1.2 原型的概念
在java 中,我们知道一个类是一种事物的抽象,通过这个类可以生成一个个具体的实例对象。我们可以理解为,类提供着生成对象的“模版”。在 JavaScript 中构造函数(constructor)就起着“模板”的作用。 注意:构造函数的首字母大写,这是约定。
//...构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
通过构造函数,我们可以生成实例化的对象。
let p1 = new Person("张三", 21)
let p2 = new Person("李四", 18)
...
在js中,我们也知道,函数也是一个对象,那么函数这个对象的模版是谁呢,其实就是我们要讲的原型这个东西。
1.3 怎么获取原型
1)使用构造函数的属性prototyoe**
function Person(name, age) {
this.name = name;
this.age = age;
}
console.dir(Person);
打印构造函数时,我们可以看到里面有好几个属性,其中一个prototype,这个属性指向的就是构造函数的原型。该属性的访问,使用`构造函数名.属性`的方式来访问,比如该案例中的构造函数Person的访问格式:`Person.prototype`。 或者按照下图方式也可以看到。这个属性,我们也称之为**显式原型**
其他属性如下:
arguments: 就是用来存储调用函数时的形参的,是一个伪数组对象
caller: 指向的是本函数的调用者,也就是谁调用本函数,就指向的谁
length: 该函数的参数个数
name: 该函数的名字
2)使用实例的隐藏属性__proto__
使用构造函数创建的每一个对象里,都隐藏着一个属性`__proto__`,该属性指向的也是构造函数的原型,我们也称之为**隐式原型**
==但是这种获取方式,在生产环境中慎用,因为并不是所有的浏览器都支持这个属性,这里我们只是学习和了解。==
function Person(name, age) {
this.name = name;
this.age = age;
}
console.log(Person.prototype);
let p1 = new Person('张三', 21);
console.log(p1.__proto__);
console.log(p1.__proto__ === Person.prototype); //true
3)使用Object.getPrototypeOf()方法
这个是最直接的方法,它返回指定对象的原型,即`[[Prototype]]`。
function Person(name, age) {
this.name = name;
this.age = age;
}
console.log(Person.prototype);
let p1 = new Person('张三', 21);
let pro = Object.getPrototypeOf(p1);
console.log(pro === Person.prototype); //true
1.4 原型的作用
每一个实例对象都有自己的属性和方法,由于无法做到数据共享,导致资源的浪费。因此原型被设计出来的作用,就是为了实现属性和方法的共享。
在原型上定义的属性和方法可以被由该构造函数创建出来的所有对象实例共享。这样可以节省内存空间,不需要为每个对象都单独的设计相同的属性或者方法了。
function Person(name, age) {
this.name = name;
this.age = age;
}
let p1 = new Person("张三", 21)
let p2 = new Person("李四", 20)
// 在原型上绑定一个gender属性, 那么所有的实例对象,也都可以`继承`这个属性
Person.prototype.gender = '女'
// 在实例对象上找该属性,如果找到了就直接使用,如果没有找到,就向上找,去实例原型上找。
console.log(p1.gender, p2.gender); // 女 女
// 在原型上绑定方法sayHi。所有的实例对象上,也都继承了这个方法
Person.prototype.sayHi = function () {
console.log("你好!我是中国人");
// console.log(this); // 函数里的this, 谁调用该函数,this就指向谁
}
p1.sayHi()
p2.sayHi()
// 通过实例修改继承原型上的属性值, 实际上是实例对象新添加了一个与原型属性同名的属性而已。实例对象并不会覆盖原型上的属性值
p1.gender = '男'
console.log(p2.gender);
// 当然,实例自己独有的属性,方法,原型上是不可能有的,也不会和其他实例共享的
p1.hello = function () {
console.log("我是实例p1的方法");
}
p1.hello()
// p2.hello() //报错 Uncaught TypeError: p2.hello is not a function
2. 原型链
2.1 原型链简介
当访问一个对象的属性或方法时,首先JavaScript引擎会从对象自身上去找,如果找不到,就会往原型中去找,即`__proto__`,也就是它构造函数的prototype中。
**如果原型中找不到呢?**
因为构造函数也是对象,实例原型也是对象,他们也都有`__proto__`,就会往原型上去找,这样就形成了链式的结构,称为**原型链**
**原型链的作用**
JavaScript中没有传统的类继承概念,而是通过原型链实现继承。一个对象可以通过原型链继承另一个对象的属性和方法。