js笔记——原型(prototype)、原型链、call/apply

  • Post author:
  • Post category:其他




原型

  • 定义:原型是function对象的一个属性,它定义了构造函数制造出对象的公共祖先。通过该构造函数产生的对象可以继承该原型的属性和方法。原型也是对象。
Person.prototype.name = "lijing";
Person.prototype.say = function () {
   console.log("prototype:say");
}

function Person() {

}
var peo1 = new Person();
console.log(Person.prototype); // {name: "lijing", say: ƒ, constructor: ƒ}
console.log(peo1.name); // lijing
peo1.say();// prototype:say
Person.prototype.name = "lijing";
Person.prototype.say = function () {
   console.log("prototype:say");
}

function Person(name) {
   this.name = name;
   this.say = function(){
       console.log("person:say");
   }
}
var peo1 = new Person("xiaoming");
console.log(peo1.name); // xiaoming
peo1.say();//person:say
  • 利用原型的特点和概念,可以提取共有属性。
// 将car的公共属性放在prototype中,在每次实例化
// 对象的时候不用重复执行赋值操作
Car.prototype.height = 1400;
Car.prototype.lang = 2900;
Car.prototype.carName = 'BWM';
// 或者这样写也可以
// Car.prototype = {
//     height:1400,
//     lang:2900,
//     carName:'BMW'
// }
function Car(color, owner) {
    this.owner = owner;
    this.color = color;
}
var car = new Car('red', 'xiaonming');
var car1 = new Car('white', 'lijing');
console.log(car); // Car {owner: "xiaonming", color: "red"}
console.log(car1); // Car {owner: "lijing", color: "white"}
console.log(car.height); // 1400
// 原型的增删改查只能通过操作Car.prototype.属性 来操作属性
  • 对象如何查看原型–>隐式属性__proto__
  • 对象如何查看对象的构造函数–>constructor
function Car(name) {
    this.name = name;
}
var c = new Car('xiaoming'); // Car {name: "xiaoming"}
console.log(c);
console.log(c.constructor);
// ƒ Car(name) {
//     this.name = name;
// }
console.log(Car.prototype);
// constructor: ƒ Car(name)
// __proto__: Object

// 可以看出构造函数是prototype的一个属性,
// 所以只打印对象的时候不会打印构造函数,但
// 是可以用过对象.constructor 来访问构造函数



__proto__属性

当我们直接打印一个对象的时候:

function Person(){
}
var person = new Person();
console.log(person);

运行结果:

Person
    __proto__:
        constructor:f Person()
        __proto__:Object

实际上,在对象创建的时候,对象内部有一个__proto__属性,指向Person.prototype,如下:

var person = {
    __proto__:Person.prototype
}

例1:

Person.prototype.name = 'xiaoming';
function Person(){
}
var person = new Person();
Person.prototype.name = 'xiaohong';
console.log(person.name);// xiaohong
// 之所以能打印出xiaohong是因为,在打印之前修改了原型的name属性,原有的name被覆盖

例2

Person.prototype.name = 'xiaoming';
function Person(){
}
var person = new Person();
Person.prototype = {
    name : 'xiaohong'
}
console.log(person.name);// xiaoming

上面的代码实际上是修改了Person.prototype的指向,令Person.prototype指向的地址发生了变化,但是__proto__指向的地址没有发生变化。相当于下面代码:

Person.prototype.name = 'xiaoming';
function Person() {
    //var this = {__proto__:Person.prototype}
}
var person = new Person();
// Person.prototype = {
//     constructor:founction Person(){};
// }
// Person.__proto__ = Person.prototype;
Person.prototype = {
    name: 'xiaohong'
}
// 这里可以知道,Person.__proto__的指向一直没有发生变化
console.log(person.name); // xiaoming

但是如果在修改了Person.prototype之后再打印person.name的话就是xiaohong

Person.prototype.name = 'xiaoming';
function Person() {
    //var this = {__proto__:Person.prototype}
}
Person.prototype = {
    name: 'xiaohong'
}
var person = new Person();
// 因为在创建person对象的时候,才将__proto__属性
// 设置为Person.prototype,此时Person.prototype已经发生了变化
console.log(person.name); // xiaohong



原型链

  1. 原型是一个对象,一个对象又可以有一个原型,所以可以形成原型链
// 原型链的最上端是Object.prototype,例如:(可见,打印结果中没有__proto__属性)
console.log(Object.prototype);
// constructor: ƒ Object()
// hasOwnProperty: ƒ hasOwnProperty()
// isPrototypeOf: ƒ isPrototypeOf()
// propertyIsEnumerable: ƒ propertyIsEnumerable()
// toLocaleString: ƒ toLocaleString()
// toString: ƒ toString()
// valueOf: ƒ valueOf()
// __defineGetter__: ƒ __defineGetter__()
// __defineSetter__: ƒ __defineSetter__()
// __lookupGetter__: ƒ __lookupGetter__()
// __lookupSetter__: ƒ __lookupSetter__()
// get __proto__: ƒ __proto__()
// set __proto__: ƒ __proto__()
  1. 原型的修改
  • 通过“子类”不可以覆盖性的修改原型,但是可以通过获取引用来修改原型。
Person.prototype.info = {
    name: "xiaoming"
}
function Person() {
}
var p = new Person();
p.info = '信息';
console.log(p.info); // 信息
console.log(p); // Person {info: "信息"}
console.log(Person.prototype.info); // {name: "xiaoming"}
// 可见,在对象上直接修改只是给先有对象增加了一个属性,并没有修改原型
// 我们可以尝试获取对象的引用,在进行修改
Person.prototype.info = {
    name: "xiaoming"
}
function Person() {
}
var p = new Person();
p.info.name = 'lijing';
console.log(p.info); // {name: "lijing"}
console.log(p); // Person {}
console.log(Person.prototype.info); // {name: "lijing"}
  • this的含义
Person.prototype = {
    name: 'a',
    sayName: function () {
        console.log(this.name);
    }
}
function Person(){
    this.name = 'b'
}
var person = new Person();
person.sayName();// b
Person.prototype.sayName();// a
// 因为当前是谁调用的sayName(),sayName中的this就指向谁
// 所以当person调用sayName()的时候,就输出Person类里面的name
// 当Person.prototype调用sayName()的时候,就调用Person.prototype的name
  1. Object.create()方法

    Object.create(pro)可以用来创建对象,参数pro为该对象的原型,如下:
var father = {
    name:'lijing',
    age:18
}
var obj = Object.create(father);
console.log(obj);
// {}
//     __proto__: 
//         age: 18
//         name: "lijing"
//         __proto__: Object
// 注意:“javascript中所有对象都继承自Object.prototype”是假命题,
// 因为用var obj = Object.create(null);创建的对象,没有__proto__属性
var obj = Object.create(null);
console.log(obj);
// {}
//   No properties
// 如果手动添加__proto__属性的话不会存在继承的效果
var obj = Object.create(null);
obj.__proto__ = {
    name:'xiaoming'
}
var son = Object.create(obj);
console.log(son);
// {}
//     __proto__:
//         __proto__:
//             name: "xiaoming"
//             __proto__: Object
console.log(son.name);// undefined
  1. toString()方法
  • 根据上面就可以解释null和undefined不能调用toString()方法的原因,因为null和undefined不是对象,就没有继承自Object.prototype,所以没有toString()方法,而数字和字符串会经过包装类包装成对象,就存在toString()方法了
  • Number类型和Boolean类型等调用的toString()方法其实是在类中重写之后的toString()方法,如果不进行重写的话,调用的是Object.prototype中的toString()方法
  • document.write()函数实际上是调用了参数的toString()方法
  1. toFixed()方法中的bug
  • 原因在于javascript在处理小数的时候会精度不准
console.log(0.14 * 100);// 14.000000000000002
// 所以产生随机数的时候,一般不用toFixed(),而用取整
console.log(Math.floor(Math.random()* 100));// 59
console.log(Math.random().toFixed(2) * 100);// 28.999999999999996



call()/apply()



作用:改变this的指向
function Person(name,age){
    this.name = name;
    this.age = age;
}
var obj={};
Person.call(obj,'hong',18);
// 在执行该函数的时候将函数里面的this全部替换为obj,
// 然后后面的参数按照形参列表传递进去
console.log(obj);// {name: "hong", age: 18}

应用:实现代码的重用

function Person(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
function Student(name, age, sex, tel, grade) {
    Person.call(this, name, age, sex);
    // 相当于执行了Person构造函数里的方法
    this.tel = tel;
    this.grade = grade;
}
var student = new Student('xiaoming',18,'男',110,100);
console.log(student);
// Student {name: "xiaoming", age: 18, sex: "男", tel: 110, grade: 100}


区别:传参列表不同

call和apply的作用一样,但是call需要把实参按照实参的个数传进去,apply需要传一个arguments数组,上面的代码可以改为:Person.apply(this,[name,age,sex])



版权声明:本文为li3455277925原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。