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个重要知识点
【构造函数是函数类型,实例对象是对象。】
-
每一个
函数数据类型
的值,都有一个天生自带的属性:prototype
(原型),这个属性的属性值是一个对象(“用来存储实例公用属性和方法”)- 普通的函数
- 类(自定义类和内置类)
-
在prototype这个对象中,有一个天生自带的属性:
constructor
,这个属性存储的是当前函数本身Fn.prototype.constructor === Fn // true
-
每一个
对象数据类型
的值,也有一个天生自带的属性:__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, "嘻", "好", "哈", "呵"]