面向对象编程

161 阅读5分钟

构造函数

构造函数相比普通函数的区别

  1. 构造函数执行,也具备普通函数执行的一面(也会像普通函数一样去执行);

  2. new函数执行的特殊性:

    • 私有上下文中要做的第一件事情是 " 创建一个空对象【是当前类的实例对象】 ";----------开辟堆内存AF0

    • 初始化this时,让上下文中的this指向创建的实例对象;---------> this执行堆地址AF0

    • 即使函数中没有return,也会默认把创建的实例对象返回。如果有return且返回的是基本类型值,默认返回的还是实例;如果return返回的是引用类型值,则返回的不是实例,以return的为主;

    • let f = nw Func;

      不加(),属于不带参数new(依然会执行函数,只是不能传递实参而已),创建出当前类的一个新实例。

      【带参数优先级:19 不带参数优先级:18】

  3. 代码执行中遇到的this.xxx=xxx都是给当前实例对象设置的私有属性;

  4. 上下文中的私有变量和实例对象没有必然的关系;

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);

原型和原型链

三句话玩转面向对象

  1. 每一个函数(构造函数[类])都天生具备一个属性 "prototype原型" ,属性值是一个对象:存储当前类供实例调用的公共属性和方法;

  2. 在原型对象上有一个内置属性 "constructor构造函数" 存储的值是当前函数本身,所以我们把类称为构造函数;

  3. 每一个对象都天生具备一个属性 " __ 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(){
    ...
}

向内置类的原型上扩展方法,存在的细节知识:

  1. 为了防止自己设定的方法覆盖内置方法,我们设置的方法名加前缀;

  2. 优势:使用起来方便,和内置方法类似,直接让实例调用即可;

  3. 方法中的this一般是当前要操作的实例(也就不需要基于形参传递实例进来了);

  4. 优势:只要保证方法的返回结果还是当前类的实例,那么我们就可以基于"链式方法"调用当前类中提供的其它方法;

    【返回结果是谁的实例,就可以继续调用谁的方法】

扩展一个数组的去重方法:

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]);
})