Javascript构造函数、new、原型以及隐式原型

  • prototype原型对象
    无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个 prototype 属性prototype既是属性也是对象(事实上,他是一个指向某个 叫做 “原型对象"的对象 的属性)
    这个原型对象的用途就是去包含所有共享的属性和方法。例子是Array(Array是一个构造数组的函数,创建数组时被执行)的slice就存在这里面

简单来说:prototype是一个容器,存着这个函数所有将共享的属性和方法。
值得注意的是 :prototype其实是一个函数对象

  • __ proto__隐式原型
    js万物皆对象,这些对象都有__ proto__属性,即:对象具有属性__ proto__,可称为隐式原型。
    一个对象的隐式原型指向构造该对象的构造函数的原型(下文通过new论证如何实现)这也保证了实例能够访问在构造函数原型中定义的属性和方法。(就像下面自定义属性和方法那样)

__ proto__指向这个实例的构造器的原型即prototype,好比数组的__ proto__就指向了Array的原型,因此我们可以通过给Array的原型添加新的方法(比如切割)来玩弄数组。
为什么只能是Array而非我自己的构造器呢?
因为除非你不用Array这个构造器来建立数组,否则这个数组的__ proto__显然是指向Array这个构造器的原型而非是你的。所以理论上你如果可以用你自己的构造器新建数组的话,那这是ok的
特殊:函数本身的构造器是Function,于是函数的__ proto__指向Function.prototype(写在js内部)

通过constructor(暂不知why) 我们还可继续为原型对象添 加其他属性和方法 (如下方2.1)

  • 构造函数:一个返回this对象的函数
    • 构造函数和普通函数的区别在于:调用方式不一样。作用也不一样(构造函数用来新建实例对象)配合new可以实现自动化

    • 构造函数的函数名与类名相同:Person( ) 这个构造函数,Person 既是函数名,也是这个对象的类名

    • 内部用this 来构造属性和方法

    • 构造流程

let Parent = function (name, age) {
    //1.创建一个新对象,赋予this,这一步是隐性的,
    // let this = {};
    //2给this指向的对象赋予构造属性
    this.name = name;
    this.age = age;
    //3.如果没有手动返回对象,则默认返回this指向的这个对象,也是隐性的
    // return this;
};

注意:上面的三步走并不是指这个函数会这么执行,而是new的时候帮它执行这三步了
是new帮它return的 当然 你可以手动创建that 然后return

  • new的过程发生了什么
    • 以构造器的prototype属性(即存放公共方法或属性的那个对象)为原型(即__ proto__),创建新对象,即:还是个空对象时,就有了那些公共属性
    • 将this(也就是上一句中的新对象)和调用参数传给构造器,执行;
    • 如果构造器没有手动返回对象,则返回第一步创建的对象

  • 原型链
    age是怎么找到的呢,首先会去person1的私有属性(本身的属性),有没有age,如果有的话就去这个属性的值,如果没有就会查找person1.__ proto__指向上的对象(即Person.prototype)有没有该属性有的话就会返回该属性的值,如果没有接着查找person1.__ proto__.__ proto__* 即指向了Object.prototype (因为__ proto__也是由Object构造器构造的嘛)然后看有没有你写的属性或者方法 没有的就返回undefined了。上述的查找过程就是作用域链。
    什么叫'直到Object.prototype指向的对象有没有'*)?
    就是一路按相同规则查找就完事了 修改在上面了。

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

var Person2 = function(){
this.name = 'pj';
Person1.prototype.name = "jy"; //写外面也行
}

var person1 = new Person1();
person1.sayName(); //"Nicholas"
person1.age; // 29
var person2 = new Person2;//()可有可无
person2.name;//pj 而非jy
  • constructor来巩固原型链
    原型对象有一个默认属性,叫做constructor,这个属性指回原构造函数。即Person.prototype.constructor===Person
    如此 p.constuctor 顺着原型链:找不到这个名字,那就找__ proto__ 而所有除了函数外的对象的__ proto__其实都指向了构造器的prototype(原理上面讲了) 于是,找到了constructor,发现,constructor永远指向这个构造函数本身,于是,就指回了他爸Person
function Person(name) {
    this.name = name
}
// 修改原型
Person.prototype.getName = function() {}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.constructor === Person) // true
console.log(p.__proto__ === p.constructor.prototype) // true
  • 自己实现一切:
    • 先熟悉apply是什么
1.apply示例:  
  
<script type="text/javascript">   
/*定义一个人类*/   
function Person(name,age) {   
    this.name=name; this.age=age;   
}   
 /*定义一个学生类*/   
functionStudent(name,age,grade) {   
    Person.apply(this,arguments); this.grade=grade;   
}   
//创建一个学生类   
var student=new Student("qian",21,"一年级");   
//测试   
alert("name:"+student.name+"\n"+"age:"+student.age+"\n"+"grade:"+student.grade);   
//大家可以看到测试结果name:qian age:21 grade:一年级   
//学生类里面我没有给name和age属性赋值啊,为什么又存在这两个属性的值呢,这个就是apply的神奇之处.   
</script>   

分析: Person.apply(this,arguments);
this:在创建对象在这个时候代表的是student这个对象//为什么这时student就是对象了?
arguments:是一个数组,也就是[“qian”,”21”,”一年级”];
也就是通俗一点讲就是:用student去执行Person这个类里面的内容,在Person这个类里面存在this.name等之类的语句,这样就将属性创建到了student对象里面
以黑盒子理解:传一个对象和一堆参数(数组)进去。只要目标函数(Person)是个合格的构造函数,那他就会帮你把这个对象的属性给生成好!
拓展:call也差不多

  • 正式开始:
// 构造器函数
let Parent = function (name, age) {
    this.name = name;
    this.age = age;
};
Parent.prototype.sayName = function () {
    console.log(this.name);
};
//自己定义的new方法
let newMethod = function (Parent, ...rest) {
    //执行三步走战略
    // 1.以构造器的prototype属性为原型,创建新对象;
    let child = Object.create(Parent.prototype);
    // 2.将this和调用参数传给构造器执行 利用apply 把child的属性生成好
    Parent.apply(child, rest);
    // 3.返回第一步的对象
    return child;
};
//创建实例,将构造函数Parent与形参作为参数传入
const child = newMethod(Parent, 'echo', 26);
child.sayName() //'echo';

//最后检验,与使用new的效果相同
child instanceof Parent//true
child.hasOwnProperty('name')//true
child.hasOwnProperty('age')//true
child.hasOwnProperty('sayName')//false
  • constuctor
    可以理解为一个标记作用 方便追溯分类构造器
var arr = []
//  恒为真
arr.constructor === Array
var num = 2
//  恒为真
num.constructor === Number
var str = '123'
//  恒为真
str.constructor === String
  • instanceof检测Parent的原型是否在child的原型链中
    • constructor改变后 不会改变instanceof child instanceof Parent == true
      如何改变instanceof的结果呢?方法如下:
      Parent.prototype = {};//覆写原型 使得原型链断掉
      child.prototype.__proto__ !== Parent.prototype
  • ownProperty 用于确定某个属性是在实例上还是在原型上 如果是,返回true
  • 方法和属性要写在prototype还是构造器里?
    • 如果写在构造器:每次构造对象时,这些属性和方法将被完全重新生成并写入该对象(为了实现私有),会耗费对应的内存
    • 如果写在prototype里:每次构造对象时,会将__ proto__指过来,从而不用重新生成,也就是说,他是所有对应实例所共享的,无论是person1还是person2访问的都是相同的属性,相同的sayname(),这个过程叫做原型继承,显然,谁都可以访问,就无法私有了
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容