数据类型新的扩展&很重要的几句话
- 类——函数类型;实例——对象类型;函数也是对象,也有自己的键值对
- 每一个函数 (除ES6中的箭头函数外) 都有一个内置属性prototype(原型属性),属性值是一个对象,存储了当前类的公共属性&方法供实例调用。
- prototype堆中,如果是浏览器为其默认开辟的堆内存,则默认存在constructor属性,属性值为函数本身。言外之意:如果重写了原型对象,则没有constructor属性,为了保证结构的完整性需要手动设置constructor。
- 每一个对象都内置一个属性__proto__(原型链属性),属性值为当前实例所属类的原型。
原型链机制
以一道题为例,剖析原型与原型链的底层机制:
function Fn(){
this.x = 100;
this.y = 200;
this.getX = function(){
console.log(this.x);
}
}
Fn.prototype.getX = function(){
console.log(this.x);
}
Fn.prototype.getY = function(){
console.log(this.y);
}
let f1 = new Fn;
let f2 = new Fn;
调用当前实例对象的某个属性(成员访问),先看是否是自己的私有属性,如果是则返回私有属性的值;如果不是则沿着原型链__proto__向上查找所属类原型中的公有属性/方法,……直到查找到基类Object的原型为止。
f1.__proto__.getY
:跳过查找私有的getY方法,直接查找其所属类原型上的方法。IE中不允许使用__proto__属性。
f1.getX()
: 涉及到this时,先确定执行的是哪个方法,谁调用的这个方法,方法中的this就是谁。
检测某个属性是否是某个对象的属性
console.log(f1.hasOwnProperty("getY")); // => false
console.log(Fn.prototype.hasOwnProperty("getY")); // => true
- 对象.hasOwnPrototype(“属性”): 检测属性是否是对象中的私有属性
- 属性 in 对象: 无论是私有属性 / 公有属性,只要其原型链中有这个属性,就返回true
console.log(getY in f1); // => true
console.log(getY in Fn.prototype); // => true
new函数执行要不要加()
普通函数:
- Fn(): 普通函数执行
- Fn: 普通函数本身
构造函数:无论加不加(),都是将函数执行,并返回一个实例对象
- new Fn(): 有参数new,可以传递参数;优先级19
- new Fn: 无参数new;优先级18
基于内置类原型扩展方法
为啥要往原型上扩展方法
- 浏览器内置的方法不一定能满足所有需求。
- 方便实例调用。实例.方法 => 方法中的this: 该实例
tips
-
扩展的方法名最好加上前缀my_(或者其他的也ok),防止被替换掉。
String.prototype.my_func = function my_func(){…}
-
this的结果一定是对象类型值。如果向基本数据类型的原型上扩展方法,this会变成它相应的对象类型,但是还是按照原始方法处理即可。因为浏览器默认会调用这个对象的valueOf方法,返回其原始值。
-
返回结果如果还是当前类的实例,则还可以继续调用当前原型上的方法——链式写法(链式调用)
// 需求:写方法,实现如下功能 let n = 10; let m = n.plus(10).minus(5); console.log(m);
分析:n为数字,它可以调用plus、minus方法,说明这两个方法应该写在Number.prototype上,数字实例才可以调用。该方法为链式写法,说明方法返回的结果还是当前类的实例。
function handleNum(num){ // 这个方法对传入的num参数进行处理,保证num是数字类型值 num = Number(num); return isNaN(num) ? 0 : num; } Number.prototype.plus = function plus(num){ num = handleNum(num); return this + num; } Number.prototype.minus= function minus(num){ num = handleNum(num); return this - num; }
this的使用可以保证n一定是数字类型值,否则无法调用Number.prototype上的方法,省去了一些判断步骤。
为了不使handleNum函数暴露在全局中,我们使用闭包:
(function (){ function handleNum(num){ num = Number(num); return isNaN(num) ? 0 : num; } Number.prototype.plus = function plus(num){ num = handleNum(num); return this + num; } Number.prototype.minus= function minus(num){ num = handleNum(num); return this - num; } })();
-
对于对象,它的属性方法无论是私有的还是公有的,存在枚举特点,即是否可以被
for-in
循环遍历到:- 能遍历到——可枚举
- 不能遍历到——不可枚举
- 原型上的内置方法——不可枚举
- 内置类原型上自己扩展的方法——可枚举
let obj = { name: "cmj", age: 24 } for(let key in obj){ console.log(key); // name age } Object.prototype.AAA = function AAA(){…}; for(let key in obj){ console.log(key); // name age AAA }
如果希望忽略原型上的扩展方法 (可能有一些别人扩展到原型上的, 你并不想要),需要这么写:
for(let key in obj){ if(!obj.hasOwnProperty(key)) break; console.log(key); // name age }