面向对象(OOP)和THIS综合复习

面向对象(OOP)和THIS综合复习

一. OOP面向对象编程

在JS中面向类/实例进行程序设计,就是经典的面向对象编程

  • 类和实例的创建(构造函数模式)
  • prototype / _proto_ (原型和原型链)
  • 函数的三种角色
  • 基于内置类原型扩展方法,实现链式写法
  • 借用内置类原型上的方法,实现一些特殊的需求(例如:把类数组转换为数组)
  • 细小的知识点:instanceof / constructor / hasOwnProperty …
  • 类的继承封装和多态

构造函数

function Fn(n,m){
    let plus=n+m,
        minus=n-m;
    this.x=plus;
    this.y=minus;
    this.print=function(){
        console.log(this.x+this.y);
    }
}
let f1=new Fn(30,10);
f1.print();
console.log(f1.plus); //=>undefined
console.log(f1 instanceof Fn); //=>true

let f2=new Fn; //=>没有传递任何的实参
console.log(f1.print===f2.print); //=>false
/*
 * 构造函数执行
 * 1.开辟一个新的私有作用域
 * 2.形参赋值&变量提升
 * 3.浏览器在当前作用域中创建一个实例对象@A,并且让THIS指向它
 * 4.代码执行  this=>当前类的实例@A
 *   this.xxx=xxx都是给当前实例@A设置的私有属性
 *   除此之外的私有变量等和@A这个实例没有必然的关系
 * 5.即使我们不设置RETURN,浏览器也会默认把实例@A返回,而外面的f1/f2接收到的就是返回的实例,所以也说f1/f2是Fn这个类的实例(如果手动返回的是引用类型值,会以用户返回的为主,也就是返回的不再是Fn的实例,如果返回基本类型值,对原有的操作无影响)
 */

Fn(10,20);
window.print();
/*
 * 普通函数执行
 *  1.形成私有的栈内存(私有的作用域scope)
 *  2.形参赋值&变量提升 n=10 m=20
 *  3.代码执行 this=>window
 *  4.没有RETURN返回值
 */

原型:prototype 和 原型链:_proto_

  • 每一个函数都自带一个属性:prototype,它的属性值是一个对象
  • prototype这个对象中有一个默认的属性:constructor,存储函数本身
  • 每一个对象都自带一个属性:_proto_,属性值是所属类的prototype
    • 普通对象、数组、正则、日期等都是对象
    • 类的实例是对象 (基本数据类型值虽然是所属类实例,但不是对象)
    • 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(); //=>this:f1  =>console.log(f1.x)  =>100
f1.__proto__.getX(); //=>this:f1.__proto__  =>console.log(f1.__proto__.x)  =>undefined
f2.getY(); //=>this:f2
Fn.prototype.getY(); //=>this:Fn.prototype

函数三种角色

  • 普通函数(闭包作用域、作用域链)
  • 构造函数(类、实例、原型、原型链)
  • 普通对象(键值对)

三种角色之间没有必然的联系

//=>JS中运算符的优先级:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
function Foo() {
    getName = function () {
        console.log(1);
    };
    return this;
}
Foo.getName = function () {
    console.log(2);
};
Foo.prototype.getName = function () {
    console.log(3);
};
var getName = function () {
    console.log(4);
};
function getName() {
    console.log(5);
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

基于内置类原型扩展方法,实现链式写法

let ary = [12,13,13,12,24,13,12];
//=>ary是Array数组类的实例,所以可以调取Array.prototype上的方法,sort方法中的this是ary,当前要排序的数组实例(底层理解:sort是内置方法,它可以实现排序,ary.sort(...)本意:ary先基于__proto__找到Array.prototype上的sort方法,并且把sort方法执行,方法中的this是ary,sort方法在执行的时候,会把this对应的数组进行排序处理)
ary.sort((a,b)=>a-b);

/*
//=>slice执行的时候,方法中的this是谁,就相当于把谁克隆成一份全新的数组出来
Array.prototype.slice=function(){
    //this:当前需要操作的这个数组
    let newAry=[];
    for(let i=0;i<this.length;i++){
        newAry.push(this[i]);
    }
    return newAry;
};
*/
let newAry = ary.slice(0);
newAry = Array.prototype.slice.call(ary,0);

function fn(){
    //=>arguments类数组集合(实参集合):不是Array的实例,它就是一个对象而已,不能直接使用数组中的方法 (=>把类数组转换为数组)
    let ary = Array.prototype.slice.call(arguments,0);
    ary = [].slice.call(arguments,0);
    
    //=>借用数组原型上的FOREACH方法,实现给类数组进行循环(内置方法中的THIS是谁,其实当前方法就在操作谁)
    [].forEach.call(arguments,item=>{
        
    });
}
fn(10,20,30,40,50);

内置方法很多,但是不一定完全够用 ,很多时候我们需要自己向内置类的原型上扩展方法来实现一些需求

~function(){
    function unique(){
        //注意:this是谁就给谁去重
        let temp={};
        for(let i=0;i<this.length;i++){
            let item=this[i];
            if(typeof temp[item]!=="undefined"){
                //this.splice(i,1);
                this[i]=this[this.length-1];
                this.length--;
                i--;
                continue;
            }
            temp[item]=item;
        }
        temp=null;
        //注意:返回的结果如果还是数组,则继续可以调取Array.prototype上的其它方法,实现“链式写法”
        return this;
    }
    //往内置类原型上扩展方法:为了防止不修改原有内置的方法,我们扩展的方法名要增加对应的前缀
    Array.prototype.myUnique = unique;
}();
let ary = [12,13,13,12,24,13,12];
ary.myUnique().reverse().push('A');
//Array.prototype.myUnique.call(ary);

二、THIS问题

THIS函数执行的主体:谁执行的

THIS是谁和函数在哪执行和在哪定义都没关系,想要分清执行主体,记住以下规律即可

  • 给元素的某个事件绑定方法,当事件触发方法执行的时候,方法中的THIS是当前元素本身
  • 方法执行,看方法名前面是否有点,有点,点前面是谁,THIS就是谁;没有点THIS是WINDOW(JS在严格模式下,没有点THIS是UNDEFINED)
    • “use strict” 开启严格模式
    • 自执行函数、回调函数等方法中的THIS一般是WINDOW
  • 在构造函数执行过程中,构造函数体中的THIS是当前类的实例
  • 使用CALL/APPLY/BIND可以改变函数中的THIS指向
  • ES6箭头函数中没有自己的THIS,所用的THIS是继承上下文中的
function fn(n,m){
    this.total=n+m;
}
let obj={name:'OBJ'};

fn(10,20); //=>this:window
//obj.fn(20,30); //=>报错:obj中没有fn属性
document.body.onclick=fn; //=>点击后FN中的this:BODY
document.body.onclick=function(){
   //=>this:BODY
   fn(30,40); //=>this:window  
};

fn.call(); //=>this:window 不传或者传递null/undefined都是
fn.call(obj,10,20); //=>this:obj
fn.apply(obj,[10,20]);//=>this:obj APPLY要求传递的参数是数组
document.body.onclick=fn.bind(obj,10,20); //=>BIND是预处理THIS,此时的FN还没有执行,只是把THIS改成了OBJ,点击BODY的时候才执行的 =>“柯理化函数”(预处理机制)

构造函数中的THIS

function Fn(){
    this.x=100;
    this.y=200;
}
Fn.prototype.sum=function(){
    console.log(this.x+this.y);
};
let f = new Fn; //=>Fn中的this:f 当前类的实例
f.sum(); //=>this:f
Fn.prototype.sum(); //=>this:Fn.prototype
f.__proto__.sum(); //=>this:f.__proto__
//=>ES6的写法
class Fn{
    constructor(){
        //=>this:当前Fn类的实例
        this.x=100;
    	this.y=200;
    }
    //=>直接写的方法就是放到原型上的
    sum(){console.log(this.x+this.y);}
    //=>STATIC修饰的都是把Fn当做普通对象设置的键值对
    static unique(){}
}
Fn.prototype.name='珠峰';
Fn.age=10;

let f = new Fn;
f.sum();
Fn.unique();
//Fn();//=>Uncaught TypeError: Class constructor Fn cannot be invoked without 'new'

箭头函数中的THIS

window.name='WINDOW';
let obj={
    name:'OBJ',
    fn:()=>{
        console.log(this.name);
    }
};
obj.fn();//=>this:window
obj.fn.call(obj);//=>this:window

//==================
document.body.onclick=function(){
    //=>this:BODY
    let _this=this;
    _this.count=0;
    
    /*setTimeout(function(){
        //=>this:window
        _this.count++;
    },1000);*/
    
    setTimeout(()=>{
        //=>this:没有自己的THIS,继承下上文中的,也就是BODY
        this.count++;
    },1000);
}

关于THIS这块的问题:

  • 重写Function.prototype中内置call/apply方法(bind可以自己先研究)
  • fn1.call.call.call(fn2) …这道阿里面试题
  • 获取数组中的最大值和最小值:Math.min.apply(Math,ary) …

上一篇:sv 从0到1 oop


下一篇:Java 对象头