原型和原型链

定义

  • proto 对象中有个属性proto,被称为隐式原型,这个隐式原型指向构造改对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。按照标准,proto 是不对外公开的,但是 chrome 和firefox的引擎却将他暴露了出来成为了一个公有属性,我们可以对其进行访问和赋值。但 ie 浏览器是不能访问这个属性的,所以不推荐大家直接操作这个属性,以免造成浏览器兼容问题。(现在__proto__属性被标准化了)
proto1.jpg
  • -proto- chrom 浏览器实现的对外可访问的变量 ,firefox浏览器对外实现的可访问变量

    firefox:

proto0.jpg

chrom:

proto01.jpg
  • prototype 函数对象 独有的属性 重要的事情说三遍!三遍三遍

简单来说,在 javascript 中每个对象都会有一个 proto 属性,当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去 proto 里找这个属性,这个 proto 又会有自己的 proto,于是就这样一直找下去,也就是我们平时所说的原型链的概念,从技术上讲,这种机制被称为动态调度(dynamic dispatch)或者委托

原型对象也是普通的对象,并且也有可能有自己的原型,默认对象实际上永远不会是空的 - 它总会从Object.prototype继承一些东西。如果要创建一个无原型的词典,我们必须显式将其原型设置为null

// 不继承任何东西。
let dict = Object.create(null);
console.log(dict.toString); // undefined

如果一个原型对象的原型不为null的话,我们就称之为原型链(prototype chain)。

原型链(Prototype chain)

定义:原型链是用于实现继承和共享属性的有限对象链。

想象一个这种情况,2个对象,大部分内容都一样,只有一小部分不一样,很明显,在一个好的设计模式中,我们会需要重用那部分相同的,而不是在每个对象中重复定义那些相同的方法或者属性。在基于类[class-based]的系统中,这些重用部分被称为类的继承 – 相同的部分放入class A,然后class B和class C从A继承,并且可以声明拥有各自的独特的东西。

ECMAScript没有类的概念。但是,重用[reuse]这个理念没什么不同(某些方面,甚至比class-更加灵活),可以由prototype chain原型链来实现。这种继承被称为delegation based inheritance-基于继承的委托,或者更通俗一些,叫做原型继承。

类似于类”A”,”B”,”C”,在ECMAScript中尼创建对象类”a”,”b”,”c”,相应地, 对象“a” 拥有对象“b”和”c”的共同部分。同时对象“b”和”c”只包含它们自己的附加属性或方法。

var a = {
  x: 10,
  calculate: function (z) {
    return this.x + this.y + z
  }
};
 
var b = {
  y: 20,
  __proto__: a
};
 
var c = {
  y: 30,
  __proto__: a
};
 
// 调用继承过来的方法
b.calculate(30); // 60
c.calculate(40); // 80

原理:

如果在对象b中找不到calculate方法(也就是对象b中没有这个calculate属性), 那么就会沿着原型链开始找。如果这个calculate方法在b的prototype中没有找到,那么就会沿着原型链找到a的prototype,一直遍历完整个原型链。记住,一旦找到,就返回第一个找到的属性或者方法。因此,第一个找到的属性成为继承属性。如果遍历完整个原型链,仍然没有找到,那么就会返回undefined。

刘伟硕代码:

//demo1
let cur_time = new Date().Format("yyyy-MM-dd hh:mm:ss");

Date.prototype.Format = function (fmt) {
    var o = {
        "M+": this.getMonth() + 1, //月份
        "d+": this.getDate(), //日
        "h+": this.getHours(), //小时
        "m+": this.getMinutes(), //分
        "s+": this.getSeconds(), //秒
        "q+": Math.floor((this.getMonth() + 3) / 3), //季度
        "S": this.getMilliseconds() //毫秒
    };
    if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
    for (let k in o)
        if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
    return fmt;
}

注:

​ 1.9 属性是静态的, 他不是独立的的正则表达式属性. 所以, 我们总是像这样子使用他们RegExp.$1, ..., RegExp.$9.

属性的值是只读的而且只有在正确匹配的情况下才会改变.

括号匹配项是无限的, 但是RegExp对象能捕获的只有九个. 你可以通过返回一个数组索引来取得所有的括号匹配项.

这些属性可以在String.replace 方法中替换字符串. 在这种情况下, 不用在前面加上RegExp。下面的例子将详细说明. 当正则表达式中不包含括号, 脚本中的 $n's 就是字面上的意思 (当n是正整数).

​ 2.replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。

stringObject.replace(regexp/substr,replacement)

​ 3.substr() 方法可在字符串中抽取从 start 下标开始的指定数目的字符。

stringObject.substr(start,length)

JS 继承

1.for in 遍历整个原型链进行继承

//demo 乱序 插进来的
function ClassA(){
    this.name ="bianbumei";
    this.age="88";
    this.sayAge=function (){
        alert(`my age is ${this.age}`)
    }
}
function ClassB(){
    let instanceA=new ClassA();
    let me =this;
    for (let oneProName in instanceA){
        console.log(`${oneProName}: ${instanceA[oneProName]}`)
        me[oneProName]=instanceA[oneProName]
    }
    console.log(me)
}
let instanceB=new ClassB();
console.log("instanceB::",instanceB)

2.使用原型链进行继承

//demo2
function ClassA() {
}

ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {
    alert(this.color);
};

function ClassB() {
}

ClassB.prototype = new ClassA();

let instanceClassB=new ClassB()
console.log(instanceClassB)
//
console.log(`ClassB :${instanceClassB}`)
//demo3
function ClassA() {
}

ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {
    alert(this.color);
};

function ClassB() {
}

ClassB.prototype = new ClassA();
ClassB.prototype.name = "";
ClassB.prototype.sayName = function () {
    //this 指向方法的调用者
    alert(this.name);
};
let instanceClassB=new ClassB()
instanceClassB.name="xiaobian";
console.log(instanceClassB)
instanceClassB.sayName();
//demo4
function ClassA() {

}
    ClassA.prototype.color = "blue";
    ClassA.prototype.sayColor = function () {
        alert(this.color);
    };
//如果将赋值语句放到ClassB的构造函数中会发生什么
function ClassB() {
ClassB.prototype = new ClassA();
}

let instanceClassB1=new ClassB()
console.log(instanceClassB1)//没有对应的方法
let instanceClassB2=new ClassB()
console.log(instanceClassB2)//有了对应的方法
//这是为什么呢?
console.log(`ClassB :${instanceClassB2}`)

继承的演变过程

extends.png

1.Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto

//demos
let baba={
    name:"mayun",
    money:100*10000,
    face:"caixukun",
    car:"aotuo",
    work_count_money:function(){
        console.log(`${this.name}的钱,我数了!很好玩!总共有${this.money}`);
    }
}
let me=Object.create(baba);
me.name="liangsijie";
me.face="shiwaxinge";
me.work_count_money();
console.log(me)

Object.create()的底层实现相当于如下代码:

function create(o){
  function F(){}
  F.prototype = o;
  return new F();
}

2.原型链继承

  • 优点:父类方法可以复用
    缺点:
    • 父类的引用属性会被所有子类实例共享
    • 子类构建实例时不能向父类传递参数

3. 构造函数继承

核心:将父类构造函数的内容复制给了子类的构造函数。这是所有继承中唯一一个不涉及到prototype的继承。

优点:和原型链继承完全反过来。

  • 父类的引用属性不会被共享
  • 子类构建实例时可以向父类传递参数

缺点:父类的方法不能复用,子类实例的方法每次都是单独创建的。

new到底做了什么?

作用域中将。。。

构造函数

定义:

构造函数是一个用于创建实例,并自动设置实例的原型的函数

例子1:

function Letter(number) {
  this.number = number;
}
Letter.prototype.getNumber = function() {
  return this.number;
};
let a = new Letter(1);
let b = new Letter(2);
// ...
let z = new Letter(26);
console.log(
  a.getNumber(), // 1
  b.getNumber(), // 2
  z.getNumber(), // 26
);
proto2.jpg

例子2:


function Persion(name,job){
    this.name=name;
    this.job=job;
    
}

Persion.myName=function(){
    return "lsj";
}
Persion.prototype.age=26;
// Persion.age=27;
Persion.prototype.sayName=function(){
    return "mahuateng";
    
};
console.log(new Persion("xiaoBian","manong").sayName()); //26
console.log(new Persion("xiaoBian","manong").age); //26
console.log(new Persion("xiaoBian","manong").myName()); //Uncaught TypeError

proto3.jpg

你可能感兴趣的:(原型和原型链)