js的原型、原型链、new、Object.create 的理解

理解原型编程范式

类是先抽象后具体,也就是必须先有类,之后才是实例,然后在关注具体层面的东西。比如 JAVA 中,类是它面向对象系统的根本。

原型是先具体后抽象,将相似实例关联到一个原型对象,从而囊括较为通用的行为和属性。基于此原型,通过 “复制” 来创建新对象

JS 中,原型是面向对象系 统的根本。

JS 中怎么实现复制的呢?

JS 是通过使新对象保持对原型对象的引用来做到了 “复制”(就是新对象上面有 proto)。

JS 不是有 Class 么?

注意,JS 的 class 只是原型的语法糖

class Dog {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  eat() {
    console.log("肉骨头真好吃");
  }
}

等价于:

function Dog(name, age) {
  this.name = name;
  this.age = age;
}
Dog.prototype.eat = function() {
  console.log("肉骨头真好吃");
};

理解原型与原型链

原型编程范式的核心思想就是利用实例来描述对象,用实例作为定义对象和继承的基础。

JS 里,原型编程范式的体现就是基于原型链的继承,每个新对象都保留了对原型对象的引用。

  • 每个构造函数都拥有一个 prototype 属性,它指向构造函数的原型对象,这个原型对象中有一个 construtor 属性指回构造函数
  • 每个实例都有一个**proto属性,当我们使用构造函数去创建实例时,实例的proto** 属性就会指向构造函数的原型对象。

用例子来理解原型链:

function A() {
  this.name = "a";
  this.color = ["green", "yellow"];
}
function B() {}
B.prototype = new A();
var b1 = new B();
var b2 = new B();
// 此处是赋值,并不是读取,所以不会往上找
b1.name = "change";
// b1.color是读取,如果b1上面没有,是会往上找的
b1.color.push("black");
console.log(b2.name); // 'a'
console.log(b2.color); // ["green", "yellow", "black"]

读取一个实例的属性/方法时,它首先搜索这个实例本身,若没找到,会向上去搜索实例的原型对象;
若还没找到,它就去搜索原型对象的原型对象,一直到Object.prototype为止,这个搜索的轨迹,就叫做
原型链

new 做了啥

实例大多是被 new 出来的。

那么 new 到底做了什么呢?

  • 为这个新的对象开辟一块属于它的内存空间
  • 把函数体内的 this 指到 开辟的内存空间去
  • 将新对象的 __proto__这个属性指向对应构造函数的 prototype属性的引用,把实例和原型对象关联起来。注意这里是引用
  • 执行函数体内的逻辑,有可能创建新对象的属性和方法
  • 最后即便你没有手动 return,构造函数也会帮你把创建的这个新对象 return 出来
var A = function() {};
A.prototype.n = 1;

var b = new A();
A.prototype = {
  n: 2,
  m: 3
};
var c = new A();
console.log(b.n);
console.log(b.m);
console.log(c.n);
console.log(c.m);
//答案是1 undefined 2 3。

注意,b 实例创建的时候,构造函数的 prototype 的引用是{n:1,constructor:A},所以 b 实例的proto也是指向{n:1,constructor:A}
之后进行A.prototype ={...},切断了和旧 prototype 的关系,
而 b 却仍然保留着旧 prototype 的引用。这就是造 成 b 实例和 c 实例之间表现差异的原因。
function A() {}

function B(a) {
  this.a = a;
}

function C(a) {
  if (a) {
    this.a = a;
  }
}

A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;

console.log(new A().a); // 1
console.log(new C(2).a); // 2

console.log(new B().a); // undefined, 
解释下为啥是undefined:
因为你有在B这个构造函数传了参数,函数体内接收了这个参数a,到这里你就必须要知道new的时候到底做了什么,所以你必须看下面手写new的过程,
因为new会把 this指向新对象、执行函数体的时候把参数带进去,
const res = FnCreator.call(newObj, ...args);你都没参数传进去肯定是undefined了。

手写实现 new

回忆new 做的事:为新对象开辟内存空间、新对象的 __proto__ 指向构造函数的原型对象、函数体的this 指向新对象、执行函数体、函数体没有返回新对象则主动返回新对象

function myNew(FnCreator, ...args) {

  //1. 为新对象开辟内存空间
  let newObj = {};

  //2. 新对象的__proto__指向构造函数的原型对象,就算你不赋值就会取原本初始的原型值
  newObj.__proto__ = FnCreator.prototype;

  //3. this指向新对象、执行函数体,因为这里指向的this是newobj对象,
  const res = FnCreator.call(newObj, ...args);

  //4. 函数体没有返回新对象则主动返回新对象,如果上面call调用了构造函数体返回的是null或者空,
       就返回newObj,这就是为什么就算这里不传值d、2,还是会取到设置好的原型上面a和1,
       而且就算你构造函数体内不进行赋值也会默认取到 a和1;
  return typeof res === "object" ? res : newObj;
}

// 创建一个Dog构造函数
function Dog(name, age) {
  this.name = name;
  this.age = age;
  //就算你构造函数体内不进行赋值也会默认取到 a和1
}
Dog.prototype.eat = function() {
  console.log("肉骨头真好吃");
};

Dog.prototype.name = 'a';
Dog.prototype.name = 1;

// 创建dog实例
var dog = myNew(Dog,'d',2)  //注意,就算这里不传值d、2,还是会取到设置好的原型上面a和1

// 输出"肉骨头真好吃"
dog.eat();

最后说下js 函数不加new和加了new执行的区别:

function Person(name){
	this.name = name;
	console.log(this);
}

var person1 = new Person("Bob");
   先说说下new的:因为看了上面new的执行过程后,应该都恍然大悟了,没错,
   this会指向新的对象,而这个新对象就是这个构造函数Person函数了。
   顺便再说下,为啥 Vue、react框架为啥会用 new Vue({...})这种操作了吧,
   就是为了方便操作这个构造函数里面的各种属性方法。


var person2 = Person("bob2");
     这种直接当成调用普通函数那样的话。。。this肯定就是window了,没得说。

 Object.create 顺便说下

Object.create =  function (o) {
    var F = function () {};
    F.prototype = o;
    return new F();
};
实际内部重新new了一个对象

1. Object.create(o),如果o是一个构造函数,则采用这种方法来创建对像没有意义

2.Object.create(o),如果o是一个字面量对象或实例对象,那么相当于是实现了对象的浅拷贝

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值