原型、原型链、call和apply

一、原型

1、prototype
(1)定义:原型prototype其实是函数(function)对象的一个属性,也是对象

// 定义构造函数
function Test() {}
// 打印prototype
console.log(Test.prototype); // {constructor: ƒ}

(2)prototype是定义构造函数构造出的每个对象的公共祖先,所有被该构造函数构造出的对象都可以继承原型上的属性和方法

// 定义构造函数
function Handphone(brand) {
    this.brand = brand;
    this.screen = '18:9';
}

// 挨个添加原型属性和方法
// Handphone.prototype.rom = '64G';
// Handphone.prototype.ram = '6G';
// Handphone.prototype.screen = '16:9';
// Handphone.prototype.call = function() {
//     console.log('I am calling somebody');
// }

// 原型改进
Handphone.prototype = {
    rom: '64G',
    ram: '6G',
    screen: '16:9',
    call: function() {
        console.log('I am calling somebody');
    }
}

// 通过构造函数创造两个不同的对象
var hp1 = new Handphone('小米')
var hp2 = new Handphone('华为')

// 调用原型属性
console.log(hp1.rom); // 64G
console.log(hp2.ram); // 6G
// 调用原型方法
hp2.call(); // I am calling somebody

// this中有先找this,没有找原型
console.log(hp1.screen); // 18:9
console.log(hp2.screen);// 18:9

经验:需要配置参数写在构造函数中,公共不变的属性和方法放到原型中

(3)通过实例化对象不能修改自己祖先的prototype的属性和方法

通过子类只能查询原型,不能新增、修改、删除

// 定义构造函数
function Test() {}
Test.prototype.name = 'prototype';

// 实例化对象
var test = new Test();

console.log(test.name); // prototype
// 增加-不行
test.num = 1;
console.log(Test.prototype, test); // {name: "prototype", constructor: ƒ} Test {num: 1}
// 删除-不行
console.log(test); // Test {num: 1}
delete test.name;
console.log(Test.prototype, test); // {name: "prototype", constructor: ƒ} Test {num: 1}
// 修改-不行
test.name = 'proto';
console.log(Test.prototype, test); // {name: "prototype", constructor: ƒ} Test {num: 1, name: "proto"}

2、proto

// __proto__: 一个容器对象,属于每个实例化对象,存储在this中,
是系统给prototype定的一个键名,方便查找到prototype,可以修改
function Car() {
    // 隐式存储__proto__
    // var this = {
    //     __proto__: Car.prototype = {
    //         name: 'Benz'
    //     }
    // }
}
Car.prototype.name = 'Benz';

var car  = new Car();
console.log(car); // Car {__proto__: Object}

// 过程:this -> __proto__ -> Car.prototype -> name
console.log(car.name); // Benz

// 实例化之后更改prototype
Car.prototype = {
    name: 'BMW'
}
// 在实例化之后更改无效
console.log(car.name); // Benz

// 更改__proto__
car.__proto__ = {
    name: 'Mazda'
}
console.log(car.name); // Mazda

3、constructor

// constructor 指向构造函数本身

// 构造函数
function Car() {}

console.log(Car.prototype); // {constructor: ƒ Car()}

// 实例化对象
var car = new Car();
console.log(car.constructor); // ƒ Car() {}

4、原型的5大原则

// 1、所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了“null”以外)
var obj = {}; 
obj.a = 100;
var arr = [];
arr.a = 100;
function fn() {}
fn.a = 100;

console.log(obj, obj.a); // {a: 100} 100
console.log(arr, arr.a); // [a: 100] 100
console.log(fn, fn.a); // ƒ fn() {} 100

// 2、所有的引用类型(数组、对象、函数),都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);

// 3、所有的函数,都有一个 prototype(显式原型) 属性,属性值也是一个普通的对象
console.log(fn.prototype); // {constructor: ƒ}

// 4、所有的引用类型(数组、对象、函数),__proto__属性值指向它的构造函数的“prototype”属性值
console.log(obj.__proto__ === Object.prototype); // true

// 5、当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,
那么会去它的__proto__(即它的构造函数的prototype)中寻找。

5、一道笔试题

// this 谁用指向谁
function Car() {
    this.brand = 'Benz';
}
Car.prototype = {
    brand: 'Mazda',
    intro: function() {
        console.log('我是' + this.brand + '车');
    }
}
var car = new Car();
// this指向实例对象本身
car.intro(); // 我是Benz车
// 直接访问原型
Car.prototype.intro(); // 我是Mazda车

原型链

1、沿着__proto__寻找继承关系的链条
2、原型本身也有原型,所有对象都有原型
3、原型链的终点在Object.prototype
4、Object.prototype 里面保存了一个toString方法
// 重写toString方法
Object.prototype.toString=function(){
    //this是什么要看执行的时候谁调用了这个函数。
    console.log("I'm " + this.name + " And I'm " + this.age);
}

// 构造函数
function Foo(name,age){
    this.name = name;
    this.age = age;
}

var fn = new Foo('小明',19);
fn.toString(); // I'm 小明 And I'm 19
console.log(fn.toString === Foo.prototype.__proto__.toString); // true

console.log(fn.__proto__ === Foo.prototype); // true
console.log(Foo.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

解释:

1、fn的构造函数是Foo(),所以:
    fn._ _ proto _ _=== Foo.prototype 为true

2、Foo.prototype是一个普通的对象,它的构造函数是Object,所以:
    Foo.prototype._ _ proto _ _=== Object.prototype 为true

3、 Object.prototype 也是一个对象,但因为是原型链的终点,里面存储null,所以:
Object.prototype.__proto__ === null 为true

4、原型链:当fn调用toString()时,JS发现fn中没有这个方法,
于是它就去Foo.prototype 中去找,发现还是没有这个方法,
然后就去 Object.prototype中去找,找到了,就调用 Object.prototyp e中的t oString() 方法。

原型扩展

// 1、构造函数字面量
var a = {} 其实是 var a = new Object() 的语法糖
var a = [] 其实是 var a = new Array() 的语法糖
function Foo(){...} 其实是 var Foo = new Function(...)
// 2、对象字面量的 constructor 指向系统自带的构造函数,
var obj1 = {}
console.log(obj1); // {__proto__: {constructor: ƒ Object()}}


// 用户自定义实例化对象的 constructor 指向自定义的构造函数
function Obj() {}
var obj2 = new Obj();
console.log(obj2); // {__proto__: {constructor: ƒ Obj()}}
// 3、原型的原型是系统自带的 Object 构造出来的
function Obj() {}
var obj = new Obj();
// Obj.prototype
console.log(obj.__proto__); // {constructor: ƒ Obj(), __proto__: {constructor: ƒ Object()}}
// 4、Object.create(参数为对象 或 null) : 创建对象,自定义原型
Object.create(对象,null);

// 4.0 new 过程
(1) 实例化obj2
 (2) 调用构造函数Obj的初始化属性和方法
 (3) 指定实例对象的原型


// 4.1 自定义原型与系统指定原型
function Obj() {}
Obj.prototype.num = 1;
// 自定义原型对象
var obj1 = Object.create(Obj.prototype);
// 系统指定原型对象
var obj2 = new Obj();
// 两种创建出的对象一模一样
console.log(obj1); // Obj { __proto__: {num: 1, constructor: ƒ Obj()}}
console.log(obj2); // Obj { __proto__: {num: 1, constructor: ƒ Obj()}}


// 4.2 修改原型为自定义的对象
var test = { num: 2 }
var obj3 = Object.create(test);
console.log(obj3); // { __proto__: {num: 2, __proto__: {constructor: ƒ Object()}}}


// 4.3 将其他对象作为原型使用-继承
var obj1 = Object.create(null); // 创建obj1空对象
console.log(obj1); // {} obj1没有原型
obj1.num = 1;
var obj2 = Object.create(obj1);
console.log(obj2); // {__proto__:{num: 1}}
console.log(obj2.num); // 1


// 4.4 不是所有的对象都继承于Object.prototype
var obj1 = Object.create(null); // 创建obj1空对象
console.log(obj1); // {} obj1没有原型
// 5、__proto__只能改变,不能重新造
var obj = Object.create(null);
obj.num = 1;
var obj1 = {
    count: 2
}
obj.__proto__ = obj1;
console.log(obj); // {num: 1, __proto__: {…}} - __proto__ 深红色,表示自定义的属性,不是原型
console.log(obj.count); // undefined

方法重写:toString

// Object 原型上的 toString 方法
console.log(Object.prototype.toString.call(1)); // '[object Number]'
console.log(Object.prototype.toString.call('a')); // '[object String]'
console.log(Object.prototype.toString.call(true)); // '[object Boolean]'
console.log(Object.prototype.toString.call([1, 2])); // '[object Array]'
console.log(Object.prototype.toString.call({num:1})); // '[object Object]'

// 对象原型重写 toString()
console.log(Number.prototype.toString.call(1)); // '1'
console.log(String.prototype.toString.call('a')); // 'a'
console.log(Boolean.prototype.toString.call(true)); // 'true'
console.log(Array.prototype.toString.call([1, 2])); // '1,2'
console.log(Object.prototype.toString.call({num:1})); // '[object Object]'

call和apply

//call和apply
function test() {}
test(); // 系统隐式 test.call()

// 作用:改变this指向---借用别人的函数实现自己的功能
// 区别:后面传的参数形式不同
function Car(brand, color) {
    this.brand = brand; // newCar.brand = brand;
    this.color = color; // newCar.color = color;
    this.run = function() {
        console.log('running');
    }
}

var newCar = {
    displacement: '3.0'
};

Car.call(newCar, 'Benz', 'red'); // 将Car的this全部改为newCar
console.log(newCar); // {displacement: "3.0", brand: "Benz", color: "red", run: ƒ}

// 常用
Car.apply(newCar, ['Benz', 'red']);
console.log(newCar); // {displacement: "3.0", brand: "Benz", color: "red", run: ƒ}

// 不影响原有对象
var car = new Car('Benz', 'red');
console.log(car); // Car {brand: "Mazda", color: "color", run: ƒ}