面向对象(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) …