486 原型及原型链模式:3个重要知识点,从面向对象角度来讲解内置类,hasOwnProperty,原型链方法中的THIS问题,基于内置类的原型扩展方法

滴滴考察原型、原型链面试题

Function.prototype.a = () => {
  console.log(1)
}
Object.prototype.b = () => {
  console.log(2)
}
function A () {}
const a = new A()
a.a() // 报错
a.b() // 2
A.a() // 1

3个重要知识点

【构造函数是函数类型,实例对象是对象。】

  1. 每一个函数数据类型的值,都有一个天生自带的属性:prototype(原型),这个属性的属性值是一个对象(“用来存储实例公用属性和方法”)

    • 普通的函数
    • 类(自定义类和内置类)
  2. 在prototype这个对象中,有一个天生自带的属性:constructor,这个属性存储的是当前函数本身

    Fn.prototype.constructor === Fn // true
    
  3. 每一个对象数据类型的值,也有一个天生自带的属性:__proto__,这个属性指向“所属类的原型prototype”

    • 普通对象、数组、正则、Math、日期、类数组等等
    • 实例也是对象数据类型的值
    • 函数的原型prototype属性的值也是对象类型的
    • 函数也是对象数据类型的值
/*
 * 类:函数数据类型
 * 实例:对象数据类型的
 */
function Fn() {
  /*
   * NEW执行也会把类当做普通函数执行(当然也有类执行的一面)
   *   1.创建一个私有的栈内存
   *   2.形参赋值 & 变量提升
   *   3.浏览器创建一个对象出来(这个对象就是当前类的一个新实例),并且让函数中的THIS指向这个实例对象  => “构造函数模式中,方法中的THIS是当前类的实例”
   *   4.代码执行
   *   5.在我们不设置RETURN的情况下,浏览器会把创建的实例对象默认返回 
   */
  this.x = 100;
  this.y = 200;
  this.say = function () { }
}

Fn.prototype.eat = function () {
  console.log('吃饭睡觉打豆豆');
}

Fn.prototype.say = function () { }

var f1 = new Fn();
var f2 = new Fn();


原型链查找机制

1.先找自己私有的属性,有则调取使用,没有继续找
2.基于__proto__找所属类原型上的方法(Fn.prototype),如果还没有则继续基于__proto__往上找...一直找到Object.prototype为止


从面向对象角度来讲解内置类


hasOwnProperty

检测某一个属性名是否为当前对象的私有属性

“in” :检测这个属性是否属于某个对象(不管是私有属性还是公有属性,只要是它的属性,结果就为TRUE)

// 自己堆中有的就是私有属性,需要基于__proto__查找的就是公有属性(__proto__在IE浏览器中(EDGE除外)给保护起来了,不让我们在代码中操作它)
let ary = [10, 20, 30];
console.log('0' in ary);  // => TRUE
console.log('push' in ary); // => TRUE
console.log(ary.hasOwnProperty('0'));  // => TRUE
console.log(ary.hasOwnProperty('push')); // => FALSE,"push"是它公有的属性,不是私有的

// => TRUE,是公有属性,还是私有属性,需要看相对谁来说的
console.log(Array.prototype.hasOwnProperty('push')); 
console.log(Array.prototype.hasOwnProperty('hasOwnProperty')); // => FALSE
console.log(Object.prototype.hasOwnProperty('hasOwnProperty')); // => TRUE   

检测某个属性是否为对象的公有属性:hasPubProperty

方法:是它的属性,但是不是私有的

// 基于内置类原型扩展方法
Object.prototype.hasPubProperty = function (property) {
  // => 验证传递的属性名合法性(一般只能是数字或字符串等基本值)
  let x = ["string", "number", "boolean"],
    y = typeof property;
  if (!x.includes(y)) return false;
  // => 开始校验是否为公有的属性(方法中的THIS就是要校验的对象)
  let n = property in this,
    m = this.hasOwnProperty(property);
  return n && !m;
}
console.log(Array.prototype.hasPubProperty('push')); // => FALSE
console.log([].hasPubProperty('push')); // => TRUEa

原型链方法中的THIS问题

/*
 * 面向对象中有关私有/公有方法中的THIS问题
 *   1.方法执行,看前面是否有点,点前面是谁THIS就是谁 【确定this指向】
 *   2.把方法总的THIS进行替换 
 *   3.再基于原型链查找的方法确定结果即可
 */

function Fn() {
  // => this:f1这个实例
  this.x = 100;
  this.y = 200;
  // 构造函数中有自己的属性、方法,则用构造函数中的,不用原型对象上的
  this.say = function () {
    // 普通函数的this,看谁调用的
    console.log(this.x);
  }
}

Fn.prototype.say = function () {
  console.log(this.y);
}

Fn.prototype.eat = function () {
  console.log(this.x + this.y);
}

Fn.prototype.write = function () {
  this.z = 1000;
}

let f1 = new Fn;
f1.say(); // => this: f1  => console.log(f1.x)   => 100
f1.eat(); // => this: f1  => console.log(f1.x + f1.y)   => 300

// => this: f1.__proto__   => console.log(f1.__proto__.y),不找私有属性,在原型上找,原型上没有y属性,Object的原型上也没有   => undefined
f1.__proto__.say();

// => this: Fn.prototype   => console.log(Fn.prototype.x + Fn.prototype.y) ,undefined + undefined =>  NaN
Fn.prototype.eat();

// => this: f1   => f1.z=1000   => 给f1设置一个私有的属性z=1000
f1.write();

// => this: Fn.prototype   => Fn.prototype.z=1000   => 给原型上设置一个属性z=1000(属性是实例的公有属性)
Fn.prototype.write();
console.log(f1.z) // 1000


基于内置类的原型扩展方法:数组去重

/*
 * 基于内置类的原型扩展方法 
 *   在内置类原型上的方法,类所对应的实例可以直接调取使用,例如:实例.方法()  ary.push()
 *   如果我们也把自己写的方法放到原型上,那么当前类的实例也可以直接这样调取使用了,很方便
 * 
 * 但是也有需要注意的地方
 * 	 1.自己扩展的方法不能影响原有内置的方法(我们自己设置的方法最好加前缀: 如my)
 *   2.扩展方法中的THIS一般都是当前类的实例(也就是要操作的值):实例.方法()
 */

// 补充
let obj = { aa: 11, bb: 22 }
console.log(obj.cc) // undefined

~ function () {
  /*
   * myUnique : 实现数组去重
   *   @params
   *   @return
   *      [Array] 去重后的数组
   * by 666 on 20190805
   */
  function myUnique() {
    // 此时没有传递要操作的ARY进来,但是方法中的THIS是当前要操作的数组,因为是该数组调用该方法:ARY.MYUNIQUE()
    let obj = {};
    for (let i = 0; i < this.length; i++) {
      let item = this[i];
      // 如果obj中没有item这一项,就是undefined,不等于undefined,说明有了
      if (typeof obj[item] !== 'undefined') {
        // (1)删除重复项,会把这一项后面的所有项都往前提一位,性能差;
        // (2)下一轮循环,i++,就会空出一位,防止出现塌陷问题,i--;
        // (3)最后一项拿过来,替换当前项,当前项就不能用了,然后把最后一项删除
        this[i] = this[this.length - 1];
        this.length--;
        i--;
        continue; // 存在了,就不往里存了
      }
      obj[item] = item;
    }
    obj = null;
    // 保证当前方法执行完返回的结果依然是ARRAY类的一个实例
    return this;
  }
  // => 扩展到内置类的原型上
  Array.prototype.myUnique = myUnique;
}();

let ary = [12, 23, 13, 12, 23, 24, 34, 13, 23];
// ary.myUnique(); 返回去重后的数组(也是ARRAY类的实例)
// ary.sort((a, b)  =>  a - b); 返回排序后的数组
// => 链式写法(保证返回值依然是当前类的实例 一般都会RETURN THIS)
// ary.myUnique().sort((a, b)  =>  a - b).reverse().slice(2).push('珠峰').concat(12);// => Uncaught TypeError: ary.myUnique(...).sort(...).reverse(...).slice(...).push(...).concat is not a function  执行完push返回的是一个数字(新增后数组的长度),不是数组了,不能在继续使用数组的方法
ary.myUnique().sort((a, b) => a - b).reverse();
console.log(ary);


/* Array.prototype.push = function () {
  console.log("哈哈哈");
}
let ary = [1, 2, 3];
ary.push(100); // => "哈哈哈"
console.log(ary); // => 数组没变*/


// --------------------------


// 补充:我之前去重的老写法
function myUnique(arr) {
  let obj = {}
  arr.forEach((item, index) => {
    obj[item] = item
  })
  // {12: 12, 13: 13, 23: 23, 24: 24, 34: 34, 嘻: "嘻", 好: "好", 哈: "哈", 呵: "呵"}
  console.log(obj)
  arr = []
  for (let k in obj) {
    arr.push(obj[k])
  }
  return arr
}

let arr = ['嘻', '好', 12, 23, 13, 12, 23, 24, 34, 13, 23, '哈', '呵', '嘻', '好', '哈', '呵'];
let res = myUnique(arr)
console.log(res) // [12, 13, 23, 24, 34, "嘻", "好", "哈", "呵"]

原文地址:https://www.cnblogs.com/jianjie/p/13199653.html