Javascript中面向对象和原型原型链是怎样的
导读:本文共12861.5字符,通常情况下阅读需要43分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要:今天就跟大家聊聊有关Javascript中面向对象和原型原型链是怎样的,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。面向对象理论知识总述* 核心答案 | 基础知识要夯实编程语言1、OOP面向对象:1)java,2)python,3)C++,4)php,5)C#(ASP.NET),6)javascript -> Node.js2、POP... ...
目录
(为您整理了一些要点),点击可以直达。- (1):* 核心答案 | 基础知识要夯实
- (3):1、OOP面向对象:
- (4):1)java,
- (5):2)python,
- (10):2、POP面向过程:
- (23):1、本身存在很多“内置类”
- (37):1)AO;
- (38):2) SCOPE-CHAIN;
- (45):2)创建的对象就是这个类的一个实例
- (62):2) Fn()是把函数执行,获取其返回值;
- (67):* 检测一个属性是否为当前对象的成员
- (75):1) 优先遍历数字属性;
- (76):2) 不会遍历到Symbol属性;
- (89):1、函数数据类型
- (90):1)普通函数
- (91):2)箭头函数
- (94):2、对象数据类型
- (99):1)箭头函数是没有prototype属性的;
- (114):1)找到的方法都是相同的;
- (133):1)先确定执行哪个方法「私有|公有」;
- (134):2)再确定执行方法中的this;
- (157):1、[pro]可以传递null或者一个对象;
- (162):* 2、扩展内置类原型上的方法
- (163):1、调用的时候更加方便;
- (164):2、也更好的实现链式调用;
- (170):1、THIS的几种情况
- (171):1)事件绑定;
- (184):* 方法一:先排序
- (185):* 方法二:假设法
- (188):* 方法三:借用Math.max
- (201):1)Array.prototype.slice() 2
- (203):1)call 2)apply
- (209):1、操作一:
- (212):2)this->body;
- (215):2、操作二:
- (218):2)this->window;
- (222):1、“立即处理的思想”
- (225):2、“预先处理思想「柯理化函数」”
- (230):* 箭头函数没有自己的this
今天就跟大家聊聊有关Javascript中面向对象和原型原型链是怎样的,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
面向对象理论知识总述
* 核心答案 | 基础知识要夯实
编程语言
1、OOP面向对象:
1)java,
2)python,
3)C++,
4)php,
5)C#(ASP.NET),
6)javascript -> Node.js
2、POP面向过程:
HTML和CSS是标记语言
1)less/sass/stylus:CSS预编译语言,让CSS具备面向对象编程的特点。
2)写完的代码无法被浏览器直接识别,需要编译后(编译成为正常的CSS)在浏览器中渲染。
什么是面向对象编程?
1、对象:泛指,万物皆对象(JS中所有我们学习研究和开发的都是对象 「研究对象」);
2、类:对 “对象” 的一个细分,按照对应的功能特点,分成我们的大类和小类「类别」;
3、实例:某个类别中具体的事物;
* 关于类的“封装、继承、多态”
1)封装:把实现某个功能的代码封装到函数中,起到“低耦合高内聚”的作用
2)继承:子类及子类的实例继承了父类中的属性和方法
3)多态:函数的重载(方法名字相同,但是传递参数的个数或者类型不同,识别为两个不同的方法 -> 后台语言有这个特征,但是JS中不存在严格意义上的重载)和重写(子类重写父类的方法)
JS就是基于“面向对象”思想设计的编程语言
1、本身存在很多“内置类”
1)每一个数据类型都有一个自己所属的内置类
2)获取的元素集合或者节点集合也是有自己的类 HTMLCollection / NodeList
3)每一个元素标签都有自己所属的类
2、我们学习JS:拿出某个类的一个实例去研究和学习,当前实例研究明白后,那么当前实例所属类下的其他实例,也具备这些特点...
自定义类的创建和一些细节知识
* 核心答案 | 基础知识要夯实
自定义类(所有的类「内置类/自定义类」都是“函数数据类型”的值)
函数执行的时候基于new执行即可 “构造函数执行”。
? 例如一:普通函数 与 构造函数
functionFn(x,y){lettotal=x+y;this.x=x;this.y=y;returntotal;}//1、作为普通函数执行Fn(10,20);//2、构造函数执行//说明:f1是Fn这个类的一个实例对象letf1=newFn(10,20);console.log(f1.x,f1.y,f1.total);//说明:total只是上下文中的私有变量,和实例f1没有关系」//结果:1020undefined
画图分析:( 有图有真相 )
构造函数 VS 普通函数
1、构造函数执行,最开始会像普通函数执行一样,形成私有的上下文。
1)AO;
2) SCOPE-CHAIN;
3)形参赋值;
4)变量提升;
5)代码执行;
不同的地方:
1、创建上下文之后,浏览器默认帮助我们创建一个对象 “实例对象”。
1)把当前Fn函数当作一个类“构造函数”
2)创建的对象就是这个类的一个实例
2、初始this的时候,让this指向当前创建的实例对象。
3、在代码执行完,返回值的时候
1)如果函数没有写return,或者返回的是一个基本数据类型值,则浏览器默认,会把创建的实例对象返回;
2)如果函数本身返回的就是一个引用数据类型值,还是以自己返回的为主。
? 例如二:构造函数扩展
functionFn(x,y){lettotal=x+y;this.x=x;this.y=y;return{name:'前端学苑'};}
说明:
由于构造函数体中,默认自己返回一个引用类型值,所以f1不再是创建的Fn实例,而是自己返回的对象。
let f1 = new Fn(10, 20);
“实例 instanceof 构造函数” :检测当前实例是否属于这个类。
console.log(f1 instanceof Fn); //false
? 例如三:函数扩展
functionFn(x,y){lettotal=x+y;this.x=x;this.y=y;this.say=functionsay(){console.log(`SAY:${total}`);};}letf1=newFn(10,20);letf2=newFn;
画图分析:( 有图有真相 )
结果:
console.log(f1===f2);//falseconsole.log(f1.say===f2.say);//false
Fn VS Fn()
1) Fn代表的是函数本身(堆内存 -> ƒ Fn(x, y) {...});
2) Fn()是把函数执行,获取其返回值;
new Fn VS new Fn()
都是把Fn执行了,只是第一个没有传递实参,第二个可以传递实参而已。
1) new Fn; 运算优先级是18(无参数列表new)
2) new Fn(); 运算符优先级是19(有参数列表new)
* 检测一个属性是否为当前对象的成员
1)属性名 in 对象:不论是私有属性还是公有的属性,只要有就是true;
2)对象.hasOwnProperty(属性名):必须是对象的私有属性,结果才是true;
说明:自己扩展一个方法 hasPubProperty(对象,属性名):检测当前属性是否属于对象的公有属性(特点:必须有这个属性,而且不是私有的)(需要扩展)
属性和变量有什么共同点:
没有。属性是堆内存的成员,变量是栈内存或者上下文当中变量。
console.log('say'inf1);//trueconsole.log('toString'inf1);//trueconsole.log('total'inf1);//falseconsole.log(f1.hasOwnProperty('say'));//trueconsole.log(f1.hasOwnProperty('toString'));//falseconsole.log(f1.hasOwnProperty('total'));//false
? 例如四:函数扩展
Object.prototype.AA='前端学苑';letobj={name:'xxx',age:11,0:100,[Symbol('AA')]:200,[Symbol.toPrimitive]:function(){return0;}};
基于“for...in”循环遍历对象
1) 优先遍历数字属性;
2) 不会遍历到Symbol属性;
3) 会把自己扩展到“类原型”上的公共属性方法也遍历到「可枚举的」
for(letkeyinobj){
//在遍历过程中,遍历到公共属性,则停止遍历:
//因为for...in遍历的本意就是只遍历私有的属性即可
if(!obj.hasOwnProperty(key))break;
console.log(key);
}
letkeys=[...Object.keys(obj),...Object.getOwnPropertySymbols(obj)];keys.forEach(key=>{console.log(`属性名:${String(key)},属性值:${obj[key]}`);});
说明:
1)Object.keys(obj):获取当前对象所有非Symbol的私有属性「数组」 =>Object.getOwnPropertyNames.
2)Object.getOwnPropertySymbols(obj):获取对象所有的Symbol私有属性「数组」.
面向对象中的原型和原型链
* 核心答案 | 基础知识要夯实
1、函数数据类型
1)普通函数
2)箭头函数
3)生成器函数
4)构造函数(类)
2、对象数据类型
1)普通对象/数组对象/正则对象/日期对象...
2)实例也是对象数据类型的(排除7种原始值类型)
3)prototype/__proto__原型属性值也是对象(排除Function.prototype)
3、大部分函数(重点是构造函数) 都内置一个prototype(原型「显式原型」)的属性,属性值是一个对象,对象中存储的属性和方法,是供当前类所属实例,调用的“公共”的属性和方法
1)箭头函数是没有prototype属性的;
2)在原型对象上有一个内置的属性 constructor(构造器),属性值是当前函数本身;
4、每一个对象都内置一个__proto__(原型链「隐式原型」)的属性,属性值指向自己所属类的原型prototype对象。
1)Object.prototype这个对象的__proto__值是null,因为Object是所有对象的“基类”
只绘制“堆内存”,画图分析:( 有图有真相 )
解析说明:
每一个数组都是Array类的实例,所以每一个数组的_proto_一定指向Array.prototype;
每一个对象都是Object类的实例,所以Array.prototype对象中的_proto_属性指向Object.prototype;
原型链的查找机制
arr[1]或者 arr.push() 再或者 arr.hasOwnProperty()…
1)首先查找当前实例对象的私有属性,私有中有,获取就是私有的;
2)如果私有中没有,则浏览器默认基于_proto_找其所属类原型(prototype)上的公共属性和方法;
3)如果还找不到,则基于原型对象上的_proto_继续向上查找 … 直到找到Object.prototype为止。最终到null。
例如:arr.push <=> arr._proto_.push <=> Array.prototype.push
1)找到的方法都是相同的;
2)区别是方法执行时候,里面的this 不同;
(1)arr.push() arr首先基于原型链查找机制,找到Array.prototype上的push 方法,并且把方法执行,方法中的this -> arr;
(2)arr._proto_.push() 直接跳过私有属性的查找,找公共的,方法执行的时候,方法中的this -> arr._proto_;
Array.prototype.push() this -> Array.prototype
3)_proto_ 在IE浏览器中进行访问。(那如果代码需要用到_proto_ ,怎么在IE中用呢? 结果:不能用。)
arr.hasOwnProperty('push') -> false
Array.prototype.hasOwnProperty('push') -> true
公有还是私有属性,它是有参照物的
1)存储在自己的堆内存中的属性是 “私有的”;
2)基于_proto_查找到的属性和方法有“公有的”;
每一个数组“即是数组也是对象”,因为它们可以调用Array.prototype和Object.prototype的属性和方法。
* 构造函数、原型与实例之间的关系
关系解析说明:
每个构造函数都有一个prototype属性指向它的原型对象,原型对象的constructor 指向构造函数,通过new 构造函数 生成实例,实例的__proto__属性指向原型对象。
? 原型与原型链
functionFn(){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);};letf1=newFn;letf2=newFn;console.log(f1.getX===f2.getX);//false「都是私有的方法」console.log(f1.getY===f2.getY);//true「都是公共的方法」console.log(f1.__proto__.getY===Fn.prototype.getY);//trueconsole.log(f1.__proto__.getX===f2.getX);//falseconsole.log(f1.getX===Fn.prototype.getX);//falseconsole.log(f1.constructor);//fnconsole.log(Fn.prototype.__proto__.constructor);//Objectf1.getX();f1.__proto__.getX();f2.getY();Fn.prototype.getY();
画图分析:( 有图有真相 )
解析说明:
1)先确定执行哪个方法「私有|公有」;
2)再确定执行方法中的this;
3)最后方法执行,计算机需要的结果即可;
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
console.log(f2.y) => 200
Fn.prototype.getY()
执行的公有方法,this -> Fn.prototype
console.log(Fn.prototype.y) => undefined
重写内置new以及基于内置类原型扩展方法
* 核心答案 | 基础知识要夯实
? * 1、new执行的原理 - 面试题( 面试常问 )
functionDog(name){this.name=name;}Dog.prototype.bark=function(){console.log('wangwang');}Dog.prototype.sayName=function(){console.log('mynameis'+this.name);}/*letsanmao=newDog('三毛');sanmao.sayName();sanmao.bark();*/function_new(){//=>完成你的代码}letsanmao=_new(Dog,'三毛');sanmao.bark();//=>"wangwang"sanmao.sayName();//=>"mynameis三毛"console.log(sanmaoinstanceofDog);//=>true
解决方法一( __proto__ 在IE浏览器兼容很差,不建议使用 )
function_new(Ctor,...params){//Ctor->Dogparams->['三毛']letobj={};obj.__proto__=Ctor.prototype;//this->指向创建的实例对象基于call方法改变即可letresult=Ctor.call(obj,...params);if(/^(object|function)$/.test(typeofresult))returnresult;returnobj;}
解析说明:
1、创建一个实例对象 实例对象.__proto__===所属类.prototype;
2、会把构造函数当做普通函数执行「私有上下文、作用域链、初始THIS、形参赋值...」;
3、观察函数执行的返回值,如果没有返回值或者返回的是基本数据类型值,默认返回的都是实例对象,否则以自己返回的值为主。
Object.create([pro]):创建一个空对象,把[pro]作为当前创建空对象的__proto__的指向(把[pro]作为当前创建空对象的原型)。
1、[pro]可以传递null或者一个对象;
2、如果传递的是null,则当前空对象不具备__proto__的属性,也就是不属于任何类的实例。
? 例如:
letpro={A:10,B:20};//UncaughtTypeError:ObjectprototypemayonlybeanObjectornull:undefinedconsole.log(Object.create());console.log(Object.create(null));
解决方法二( 在IE6,7,8浏览器不兼容 )
function_new(Ctor,...params){letobj=Object.create(Ctor.prototype);letresult=Ctor.call(obj,...params);if(/^(object|function)$/.test(typeofresult))returnresult;returnobj;}
解决方法三 ( 兼容性比较好 )
//重写的方法只考虑pro传递的是一个对象Object.create=function(pro){functionProxy(){}Proxy.prototype=pro;returnnewProxy;};function_new(Ctor){//获取除第一个实参以外,剩余传递的参数信息,以数组的形式保存到params中varparams=[].slice.call(arguments,1);//Object.create兼容IE低版本浏览器,需要改写varobj=Object.create(Ctor.prototype);//基于apply既可以改变this,也可以把数组中的每一项传递给函数varresult=Ctor.apply(obj,params);if(/^(object|function)$/.test(typeofresult))returnresult;returnobj;}
* 2、扩展内置类原型上的方法
1、调用的时候更加方便;
2、也更好的实现链式调用;
* 注意:自己编写的方法会覆盖内置的方法,所以自己命名的时候需要注意,一般都是设置前缀,例如:myUnique。
? 常用数组去重方法
functionunique(arr){//首先基于Set结构去重,最后转换为数组letresult=newSet(arr);result=Array.from(result);returnresult;}letarr=[1,2,3,2,3,4,2,3,4,2,1,2,3,4,5,3,4];letresult=unique(arr);console.log(result);
? 先去重,再排序 - 面试题( 面试常问 )
Array.prototype.unique=functionunique(){//this->arr一般是当前操作类的实例letresult=newSet(this);result=Array.from(result);returnresult;//返回的结果还是一个数组,则可以继续调用数组的其它方法->“链式调用”};
//先去重,再排序//+sort是Array.prototype上的方法,所以数组可以直接调用letarr=[1,2,3,2,3,4,2,3,4,2,1,2,3,4,5,3,4];letresult=arr.unique().sort((a,b)=>a-b);console.log(arr,result);
THIS情况汇总及CALL、APPLY、BIND的应用
* 核心答案 | 基础知识要夯实
1、THIS的几种情况
1)事件绑定;
2)函数执行:1)自执行函数 2)回调函数;
3)构造函数执行;
4)基于call /apply /bind 改变函数中的this;
5)箭头函数中没有自己的this,所用到的this是使用其上下文中的;
说明:Function.prototype -> call/apply/bind 所有的函数都可以调取这三个办法。
Function.prototype.call=functioncall(context){//this->fn//context->obj//...};
? 基于call /apply /bind 改变函数中的this
window.name='WINDOW';letobj={name:'前端学苑',age:2};functionfn(x,y){console.log(this,x+y);}fn();//this->windowobj.fn();//UncaughtTypeError:obj.fnisnotafunction
fn.call(obj);//this->objfn.call(obj,10,20);//this->objx->10y->20fn.call();//this->window严格模式下undefinedfn.call(null);//this->window严格模式下null「传递的是undefiend也是如此」fn.call(10,20);//this->10「对象」x->20y->undefined
解析说明:
fn.call(obj);
底层处理方式:fn先基于__proto__找到Function.prototype.call,把call方法执行的时候,call方法内部实现了一些功能:会把fn执行,并且让fn中的this变为第一个实参值。
* apply的作用和细节上和call一样,只有一个区别:传递给函数实参的方式不一样。
fn.call(obj,10,20);fn.apply(obj,[10,20]);
最后结果和call是一样的,只不过apply方法执行的时候要求:传递给函数的实参信息都要放置在一个数组中,但是apply内部也会向call方法一样,把这些实参信息一项项的传递给函数。
? 需求:获取数组中的最大值
letarr=[10,30,15,36,23];
* 方法一:先排序
arr.sort(function(a,b){returnb-a;});letmax=arr[0];console.log('数组中的最大值是:'+max);
* 方法二:假设法
第一种方法:
letmax=arr[0];for(leti=1;i<arr.length;i++){letitem=arr[i];if(item>max){max=item;}}console.log('数组中的最大值是:'+max);
第二种方法:
letmax=arr.reduce((result,item)=>{returnitem>result?item:result;});console.log('数组中的最大值是:'+max);
* 方法三:借用Math.max
第一种方法:
Math.max(10,30,15,36,23)->36获取一堆数中的最大值Math.max([10,30,15,36,23])->NaN传递一个数组是不行的
第二种方法:ES6展开运算符
letmax=Math.max(...arr);console.log('数组中的最大值是:'+max);
第三种方法:基于apply的特点
letmax=Math.max.apply(null,arr);console.log('数组中的最大值是:'+max);
第四种方法:字符串拼接成为最终想要的表达式
letstr=`Math.max(${arr})`;letmax=eval(str);console.log('数组中的最大值是:'+max);
? 需求:把类数组集合转换为数组集合
重写内置的slice,实现浅克隆;
Array.prototype.slice=functionslice(){//重写内置的slice,实现浅克隆//this->aryletarr=[];for(leti=0;i<this.length;i++){letitem=this[i];arr.push(item);}returnarr;};letary=[10,20,30];letnewAry=ary.slice();//不传递或者传递0->数组的浅克隆console.log(newAry,newAry===ary);
画图分析:( 有图有真相 )
区别在于:
1、内置代码中用的是this,自己写的代码中用的是argument。
2、如果我们可以 让内置的slice执行,并且把方法中的 this改变为arguments -> 这样其实就是把类数组集合克隆(转换)为数组。
3、让内置slice执行:找到slice,加小括号一执行即可。
1)Array.prototype.slice() 2)[].slice()
改变方法中的this
1)call 2)apply
4、[].slice.call(arguments) 把类数组转换为数组
重要:数组中大部分方法,都可以基于这样的原理(改变this),实现类数组的借用。原因:类数组除了不是Array的实例,和数组的结果是一致的,所以操作数组的一些代码(类似于循环等操作)也一定适用于类数组,所以可以实现方法的借用。
求和第一种方法:
functionsum(){//arguments:实参集合,它是一个类数组,不是Array的实例,所以不能直接调用Array.prototype上的方法,但是结构和数组非常的相似,都是索引+length//第一种方法letarr=[];for(leti=0;i<arguments.length;i++){letitem=arguments[i];arr.push(item);}//第二种方法//letarr=[].slice.call(arguments);returnarr.reduce((result,item)=>item+result);}lettotal=sum(10,20,30,40);
求和第二种方法,ES6实现:
functionsum(...arr){//第一种方法:...arr基于剩余运算符获取的实参集合本身就是一个数组//第二种方法:Array.from:可以把一个类数组(或者Set)转换为数组//letarr=Array.from(arguments);//第三种方法:基于展开运算符把类数组中的每一项拿出来,分别赋值给数组//letarr=[...arguments];returnarr.reduce((result,item)=>item+result);}lettotal=sum(10,20,30,40);console.log(total);//结果:100
? this指向的例子
letobj={name:'前端学苑',age:11};functionfn(x,y){console.log(this,x,y);}
1、操作一:
document.body.onclick=fn;
分析:
1)事件绑定的时候方法是没有执行的,只有事件触发,浏览器会帮助我们把方法执行;
2)this->body;
3)x->MouseEvent 事件对象「浏览器不仅帮助我们把方法执行,而且还把存储当前操作的信息的事件对象传递给函数」;
4)y->undefined;
2、操作二:
setTimeout(fn,1000);
分析:
1)设置一个定时器(此时绑定的函数没有执行,此时只是绑定一个方法),1000MS后,浏览器会帮助我们把fn执行;
2)this->window;
3)x->undefined;
4)y->undefined;
我们期望:不论是事件触发,还是定时器到时间,执行对应的方法时,可以改变方法中的this,以及给方法传递实参信息。
1、“立即处理的思想”
直接下属这种操作办法是不可以的:call/apply在处理的时候,会把函数立即执行,也就是在事件绑定或者设置定时器的时候,fn就执行了,而不是等待事件触发或者定时器到时间后再执行 “立即处理的思想”。
代码如下:
document.body.onclick=fn.call(obj,10,20);setTimeout(fn.call(obj,10,20),1000);
2、“预先处理思想「柯理化函数」”
我们绑定方法的时候(不论是事件绑定还是设置定时器),先绑定一个匿名函数,事件触发或者达到时间,先把匿名函数执行,在执行匿名函数的时候,再把我们需要执行的fn执行,此时就可以基于call/apply改变this和参数信息了。
代码如下:
document.body.onclick=function(ev){//this->bodyfn.call(obj,10,20,ev);};setTimeout(function(){//this->windowfn.call(obj,10,20);},1000);
bind相当于call/apply来讲,并不会把函数立即执行,只是实现处理了要改变的this和参数,一切的执行还是按照原有的时间或者触发节点进行。
代码如下:
document.body.onclick=fn.bind(obj,10,20);setTimeout(fn.bind(obj,10,20),1000);
* 箭头函数没有自己的this
? 实例一:
letobj={name:'前端学苑',age:11,fn:function(){//this->objletthat=this;returnfunction(){//this->window//如果需要改变obj.name,可以用that替换thisthat.name='FE2020';console.log(this);};}};letf=obj.fn();f();
? 实例二:
letobj={name:'前端学苑',age:11,fn:function(){//this->objreturn()=>{this.name='FE2020';console.log(this);//{name:'FE2020',age:11,fn:f}};}};letf=obj.fn();f.call(100);
说明:
箭头函数没有this(方法执行的时候不存在初始this这一项操作),所以基于call/apply操作它都是无用的,没有this。
看完上述内容,你们对Javascript中面向对象和原型原型链是怎样的有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注亿速云行业资讯频道,感谢大家的支持。
Javascript中面向对象和原型原型链是怎样的的详细内容,希望对您有所帮助,信息来源于网络。