构造函数
构造函数相比普通函数的区别:
-
构造函数执行,也具备普通函数执行的一面(也会像普通函数一样去执行);
-
new函数执行的特殊性:
-
私有上下文中要做的第一件事情是 " 创建一个空对象【是当前类的实例对象】 ";----------开辟堆内存AF0
-
初始化this时,让上下文中的this指向创建的实例对象;---------> this执行堆地址AF0
-
即使函数中没有return,也会默认把创建的实例对象返回。如果有return且返回的是基本类型值,默认返回的还是实例;如果return返回的是引用类型值,则返回的不是实例,以return的为主;
-
let f = nw Func;
不加(),属于不带参数new(依然会执行函数,只是不能传递实参而已),创建出当前类的一个新实例。
【带参数优先级:19 不带参数优先级:18】
-
-
代码执行中遇到的this.xxx=xxx都是给当前实例对象设置的私有属性;
-
上下文中的私有变量和实例对象没有必然的关系;
function Func(x,y){
let sum = x + y;
this.total = sum;
this.say = function(){}
}
let fun1 = new Func(1,2);
console.log(fun1); // Func {total:3,say:f}
console.log(fun1.sum); //undefined sum是上下文中的私有变量,和实例没有必然的关系,只有this.xxx=xxx才是给实例设置的私有属性
console.log(fun1.total); //3
let fun2 = new Func;
console.log(fun2); // Func {total:NaN,say:f}
console.log(fun1.say === fun2.say); //false ----说明say为私有属性
验证某个属性是否为当前对象的属性:in / hasOwnProperty
- in——不论是私有还是公有,只要是它的属性结果就是true;
- hasOwnProperty——只有是它的私有属性,结果才是true;
检测当前实例是否属于当前类:instanceof
console.log(fun2 instanceof Func);
原型和原型链
三句话玩转面向对象
-
每一个函数(构造函数[类])都天生具备一个属性 "prototype原型" ,属性值是一个对象:存储当前类供实例调用的公共属性和方法;
-
在原型对象上有一个内置属性 "constructor构造函数" 存储的值是当前函数本身,所以我们把类称为构造函数;
-
每一个对象都天生具备一个属性 " __ proto __隐式原型/原型链",属性指向自己所属类的原型对象;
实例.__ proto __ === 所属类.prototype
- 函数类型:普通函数 / 构造函数(类) / 内置类
- 对象类型:普通对象 / 数组对象 / 正则对象 / 日期对象 / prototype / __ proto __ / 函数也是对象 / 类的实例也是对象(排除基本数据类型值的特殊性)/ 万物皆对象
知识点:
- Object作为所有对象的基类,它原型对象上的__ proto__要指也是指向自己的原型(也就是自己),这样没有意义,所以给其赋值为null;
- 对象本身存储的键值对,相对于对象来讲是私有的属性;
原型链机制
概念:访问对象的某个成员,首先看是否为私有的,如果是私有的则找的就是私有的;如果不是,则基于__ proto __ 找所属类.prototype上的公共属性方法....如果还没有,则基于__ proto __继续向上查找,直到找到Object.prototype为止。
原型上的属性方法:相对于实例来讲是公有的,相对于自己来讲是私有的;
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;
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.__proto__.getX === f2.getX);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY();
内置new和Object.create的实现
//Func:要操作的类(最后创建这个类的实例)
//args:存储未来传递给Func类的实参信息
function _new(Func,...args){
//1.创建一个Func的实例对象(实例.__proto__ = 类.prototype)
let obj = {};
obj.__proto__ = Func.prototype;
//2.把Func当作普通函数执行,改变this指向,指向创建的实例
let result = Func.call(obj,...args);
//3.分析函数执行的返回值(无返回值/或者返回的是原始值类型则默认都返回创建的实例,否则以函数返回的为主)
if(result!=null && /^(object|function)$/.test(typeof result)){
return result;
}
return obj;
}
//在IE浏览器中禁止使用__proto__,防止改变原型指向导致原型错乱,也可以理解为IE并没有提供给我们这个属性
//【__proto__是浏览器实现的指的是谷歌实现了__proto__机制,完成原型查找,IE中是按照其它机制实现的,所以不能用__proto__】
//因此第一步可做以下优化处理
//优化方案
function _new(Func,...args){
//1.创建一个Func的实例对象(实例.__proto__ = 类.prototype)
let obj = Object.create(Func.prototype);
//2.把Func当作普通函数执行,改变this指向,指向创建的实例
let result = Func.call(obj,...args);
//3.分析函数执行的返回值(无返回值/或者返回的是原始值类型则默认都返回创建的实例,否则以函数返回的为主)
if(result!=null && /^(object|function)$/.test(typeof result)){
return result;
}
return obj;
}
Object.create([Object]):创建一个空对象x,并且把[object](这个值需要是个对象)作为新的空对象的原型链指向;
x.__ proto __ = [Object]
Object.create(null):创建一个没有原型/原型链的空对象,不是任何类的实例;
Object.create()不兼容,自己实现Object.create:
Object.create = function create(prototype){
//不支持null
if(prototype == null || typeof prototype !== 'object'){
throw new TypeError(`Object prototype may only be an Object:${prototype}`);
}
//创建一个类,创建这个类的实例,实例.__proto__ = 类.prototype,让类.prototype等于传递的prototype;
function Temp(){}
Temp.prototype = prototype;
return new Temp;
}
基于内置类原型扩展方法
Array.prototype.Func = function Func(){
...
}
向内置类的原型上扩展方法,存在的细节知识:
-
为了防止自己设定的方法覆盖内置方法,我们设置的方法名加前缀;
-
优势:使用起来方便,和内置方法类似,直接让实例调用即可;
-
方法中的this一般是当前要操作的实例(也就不需要基于形参传递实例进来了);
-
优势:只要保证方法的返回结果还是当前类的实例,那么我们就可以基于"链式方法"调用当前类中提供的其它方法;
【返回结果是谁的实例,就可以继续调用谁的方法】
扩展一个数组的去重方法:
Array.prototype.Func = function Func(){
//this指向arr
let newArr = [...new Set(this)];
//let newArr = Array.from(new Set(this));
return newArr;
}
let arr = [1,2,3,2,3,1,2,4,5];
arr.Func();
arr.Func().reverse();
for...in遍历对象,所有可以被枚举的属性都可以遍历到(大部分私有属性和自己向内置类原型上扩展的属性),所以处理for...in循环时需要加hasOwnProperty判断;
Object.prototype.test = function test(){}
let obj = {
name:'张三',
age:18
}
for(let key in obj){
if(!obj.hasOwnProperty(key)) break; //添加该判断来阻止遍历非私有属性
console.log(key,obj[key]); //此时test也可以遍历到
}
//Object.keys():获取所有私有的属性
Object.keys(obj).forEach(key=>{
console.log(key,obj[key]);
})