JavaScript 知识点集锦一

本文详细讲解了原型链的概念、原型对象的作用,展示了__proto__、prototype和constructor的关系,剖析了new运算符的工作原理,介绍了bind、call、apply的区别,以及如何实现继承和instanceOf的实例。通过实例演示,探讨了组合继承的优化策略。


原型链

概念

原型链就是:由原型对象组成,每个对象都有__proto__属性,指向了创建该对象的构造函数的原型__proto__将对象连接起来组成了原型链。是一个用来实现继承和共享属性的有限的对象链

  • 属性查找机制: 当查找对象的属性时,如果实例对象自身不存在该属性,则沿着原型链往上一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查找,直至最顶级的原型对象Object.prototype,如还是没找到,则输出undefined
  • 属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性,如果需要修改原型的属性时,则可以用:b.prototype.x=2;但是这样会造成所有继承于该对象的实例的属性发生改变

原型对象和原型链他们之间到底起什么作用

如果构造函数中有很多属性和方法,那么构造函数所有的实例化对象都是公用这些属性和方法的,当有多个实例想用共用这些东西的时候,每个实例都拷贝一份,就造成极大的资源浪费,那是不是可以考虑存把这些需要共用的属性和方法放到一个共同的东西上。这个共同的东西就是原型对象(prototype)

proto 、 prototype 和 constructor的关系

  • 每当定义一个函数数据类型(普通函数, 类)时候,都会自带prototype 属性,该属性指向函数的原型对象
  • 当函数经过new调用时,这个函数就成为了构造函数,返回一个全新的实例对象,这个实例对象有一个__proto__属性,指向构造函数的原型对象
  • JavaScript对象通过__proto__ 指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条, 即原型链。
const testFunction = () => {
    class test {
      x: string;
      __proto__: any;
      prototype: any;
      constructor() {
        this.x = '1'
      }
    }
    const testA = new test();

    console.log(testA.__proto__) // 指向生成该实例的构造函数(TestA)的原型对象
    console.log(test.prototype) // 该构造函数的原型对象
    console.log(testA.constructor) // 指向构造函数本身
    console.log(testA.constructor.prototype) // 指向构造函数的原型对象
    console.log(testA.__proto__ === test.prototype) // true
  }
  testFunction();

new操作符

什么是new运算符

new 运算符创建了一个用户定义的对象类型实例,或者内置具有构造函数的对象类型实例

new 运算符做了什么?

  • 创建了一个空对象{}
  • 将空对象的原型地址__proto__指向构造函数的原型对象prototype
  • 将步骤1新创建的对象作为this的上下文
  • 如果该函数没有返回对象,则返回this。

实现new

// 测试的构造函数
function A(name) {
  this.name = name;
  return this.name;
}

// 实现的new方法
function MyNew() {
  // 声明一个空对象
  var obj = {};
  // 通过arguments 调用 数组的shift 方法来获取MyNew方法的第一个参数,即传入的构造函数;
  var constructorFunction =[].shift.call(arguments);
  // 将当前实例的隐式原型指向传入的构造函数的显式原型
  obj.__proto__ = constructorFunction.prototype;
  // 改变构造函数的this指向到实例并且执行构造函数
  var res = constructorFunction.apply(obj, arguments);
  // new 方法返回对象因此res为对象时返回res,否则返回obj
  return res instanceof Object ? res : obj;
}

const my = MyNew(A, 'aa');
console.log(my.__proto__);

bind、apply与call

概念

  • bind()方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this被指定为 bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用
  • call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数
  • apply() 方法调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供
    的参数

区别

  • bind 绑定函数后不会立即执行,返回值是返回一个原函数的拷贝,并拥有指定的 this 值和初始参数,applycall返回值是调用有指定this值和参数的函数的结果
  • callbind接受的是参数列表即例如: bind(this, ...args)
  • apply 接受的是参数数组或类数组对象 (apply, args)

手写bind、call、apply


Function.prototype.myBind = function (objThis, ...params) {
    const thisFn = this; // 存储源函数以及上方的params(函数参数)
    // 对返回的函数 secondParams 二次传参
    let fToBind = function (...secondParams) {
        const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用
        const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上
        return thisFn.call(context, ...params, ...secondParams); // 用call调用源函数绑定this的指向并传递参数,返回执行结果
    };
    if (thisFn.prototype) {
        // 复制源函数的prototype给fToBind 一些情况下函数没有prototype,比如箭头函数
        fToBind.prototype = Object.create(thisFn.prototype);
    }
    return fToBind; // 返回拷贝的函数
};


Function.prototype.call = function (context, ...args) {
  let context = context || window;
  let fn = Symbol('fn');
  context.fn = this;

  let result = eval('context.fn(...args)');

  delete context.fn
  return result;
}

Function.prototype.apply = function (context, args) {
  let context = context || window;
  context.fn = this;
  let result = eval('context.fn(...args)');

  delete context.fn
  return result;
}

继承

call方法实现

function Parent1() {
	this.name = 'parent1'
}
function Child1(){
    Parent1.call(this);
    this.type = 'child1'
 }
console.log(new Child1); // { type: 'parent', name: 'child1' }

不足: 子类虽然能够拿到父类的属性值,但是问题是父类原型对象中一旦存在方法那么子类无法继承

原型链实现

function Parent2() {
  this.name = 'parent2';
  this.play = [1, 2, 3]
}
function Child2() {
  this.type = 'child2';
}
Child2.prototype = new Parent2();

不足:其中一个引用子类的属性改变,其他子类的属性都会改变

将前两种组合

  function Parent3 () {
    this.name = 'parent3';
    this.play = [1, 2, 3];
  }
  function Child3() {
    Parent3.call(this);
    this.type = 'child3';
  }
  Child3.prototype = new Parent3();
  var s3 = new Child3();
  var s4 = new Child3();
  s3.play.push(4);
  console.log(s3.play, s4.play);

不足:之前的问题都得以解决。但是这里又徒增了一个新问题,那就是Parent3的构造函数会多执行了一次(Child3.prototype = new Parent3();)

组合继承的最优实现

function Parent5 () {
    this.name = 'parent5';
    this.play = [1, 2, 3];
  }
  function Child5() {
    Parent5.call(this);
    this.type = 'child5';
  }
  Child5.prototype = Object.create(Parent5.prototype);
  Child5.prototype.constructor = Child5;

instanceOf

概念

instanceOf运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

实现

function myInstanceof(left: any, right: { prototype: object | null }) {
  const baseType = ['string', 'number', 'boolean', 'undefined', 'symbol', 'bigint']; // 基础类型返回false
  if (baseType.includes(typeof left) || left === null) return false;
  let proto = Object.getPrototypeOf(left);
  // Object.getPrototypeOf(left) 实质上是返回left的构造函数的原型对象,即 left.__proto__
  console.log(left.__proto__ === proto); // true
  while (true) {
    if (proto === null) return false; // 向上查询原型链,到null代表无上一层,没有结果则说明非当前传入的类型, 返回false
    if (proto === right.prototype) return true; // 原型链中构造函数的原型对象存在当前传入的构造函数的原型对象,返回true
    proto = Object.getPrototypeOf(proto);
  }
}

console.log(myInstanceof({}, Object)); // true
console.log({} instanceof Object); // true

参考文章

(建议收藏)原生JS灵魂之问, 请问你能接得住几个?(上)

(建议精读)原生JS灵魂之问(中),检验自己是否真的熟悉JavaScript

js基础-面试官想知道你有多理解call,apply,bind?[不看后悔系列]

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值