700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > js面向对象以及编程范式 js高级部分

js面向对象以及编程范式 js高级部分

时间:2024-03-12 04:50:07

相关推荐

js面向对象以及编程范式 js高级部分

面向对象

面向对象编程(Object Oriented Programming , OOP) 是一种编程范式,它将代码分为具有属性和方法的对象。这种方式的好处是:将相关代码片段封装到对象中,由对象来维护在程序里面的生命状态。对象可以按需被复用或者被修改。

编程范式

在此之前呢先介绍一下,什么叫做编程范式。

所谓编程范式(programming paradigm),指的是计算机编程的基本风格或典范模式。借用哲学的属于,如果说每一个编程者都在创造虚拟世界,那么编程范式就是他们置身其中自觉不自觉采用的世界观和方法论。

在我们的编程语言里面,根据编程范式来分类大致可以分为两大类:命令式编程声明式编程

1.命令式编程

一直以来,比较流行的都是命令式编程。所谓命令式编程,就是以命令为主,给机器提供一条又一条的命令序列让其原封不动的执行。程序执行的效率取决于执行命令的数量。

一句话概括:命令式编程就是命令**"机器"如何去做事情,这样不管你想要的是什么(what),它都会按照你的命令实现。**

我们常见的命令式编程有C语言,C++,Java,C#

2.声明式编程

所谓声明式编程就是告诉**“机器” 你想要什么,让机器想出如何去做(how)**

在声明式编程里面又可以分为2个大类:领域专用语言和函数式编程。

领域专用语言:

英语全称为 domain specific language ,简称SDL。主要是指对一些对应专门领域的高层编程语言,和通用编程语言的概念相对。DSL对应的专门领域(Domain)一般比较狭窄,或者对应某个行业,或者对于某一类具体应用程序,比如数据库等。我们常见的有HTML、CSS、SQL等。

函数式编程:

所谓函数式编程,简单来讲,就是一种编程模型,将计算机运算看作是数学中函数的计算。在函数式编程中,函数是基本单位,是第一型,它几乎被用做一切,包括最简单的计算,甚至连变量都别计算索取代。在函数式编程中,变量只是一个名称,而不是一个存储单元,这是函数式编程与传统的命令式编程最典型的不同之处。

随着web开发中高并发的需求越来越多,多核并行的程序涉及被推倒前线,而传统的命令式编程天生的缺陷使得并行编程模型变得非常复杂,使程序员不堪其重。函数式编程在解决兵法程序上面却有着天然的优势,代码简洁优雅,但是缺点就是学习门槛高。

面向对象

一、面向对象基本特征

封装:也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。继承:通过继承创建的新类称为“子类”或“派生类”。继承的过程,就是从一般到特殊的过程。多态:对象的多功能,多方法,一个方法多种表现形式。Javascript是一种基于对象(object-based)的语言。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类)—–es6以前是这样的。所以es5只有使用函数模拟的面向对象。

二、对象实例化方式

原始模式:这样的写法有两个缺点,一是如果多生成几个(100个!)实例,写起来就非常麻烦;二是实例与原型之间,没有任何办法,可以看出没有什么联系。

var Car = {color: 'red',//车的颜色wheel: 4,//车轮数量}var Car2 = {color: 'blue',wheel: 4,}alert(Car.color);//red

原始模式的改进:通过写一个函数,解决代码重复的问题。

function createCar(color,wheel) {return {color:color,wheel:wheel}}//然后生成实例对象,就等于是在调用函数:var cat1 = createCar("红色","4");var cat2 = createCar("蓝色","4");alert(cat1.color);//红色

工厂模式

function createCar(color,wheel){//createCar工厂var obj = new Object;//或obj = {} 原材料阶段obj.color = color;//加工obj.wheel = wheel;//加工return obj;//输出产品}//实例化var cat1 = createCar("红色","4");var cat2 = createCar("蓝色","4");alert(cat1.color);//红色

构造函数模式:为了解决从原型对象生成实例的问题,Javascript提供了一个构造函数(Constructor)模式。 所谓”构造函数”,其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。加new执行的函数构造内部变化:自动生成一个对象,this指向这个新创建的对象,函数自动返回这个新创建的对象

function CreateCar(color,wheel){//构造函数首字母大写//不需要自己创建对象了this.color = color;//添加属性,this指向构造函数的实例对象this.wheel = wheel;//添加属性//不需要自己return了}//实例化var cat1 = new CreateCar("红色","4");var cat2 = new CreateCar("蓝色","4");alert(cat1.color);//红色

三、构造函数注意事项

此时CreateCar称之为构造函数,也可以称之类,构造函数就是类 。cat1,cat2均为CreateCar的实例对象。CreateCar构造函数中this指向CreateCar实例对象即 new CreateCar( )出来的对象。必须带new 。构造函数首字母大写,这是规范,官方都遵循这一个规范,如Number() Array()。contructor:这时cat1和cat2会自动含有一个constructor属性,指向它们的构造函数,即CreateCar。

alert(cat1.constructor == CreateCar); //truealert(cat2.constructor == CreateCar); //true

每定义一个函数,这个函数就有一个 prototype 的属性{},proto指向被实例化的构造函数的prototype,prototype默认带constructor属性,constructor指向构造函数。

instanceof 运算符:object instanceof constructor运算符,验证构造函数与实例对象之间的关系。

alert(cat1 instanceof CreateCar ); //truealert(cat2 instanceof CreateCar ); //true

四、构造函数的问题

构造函数方法很好用,但是存在一个浪费内存的问题。如果现在为其再添加一个方法showWheel。那么,CreateCar就变成了下面这样,这样做有一个很大的弊端,对于每一个实例对象,showWheel都是一模一样的内容,每一次生成一个实例,都必须生成重复的内容,多占用一些内存。这样既不环保,也缺乏效率。

function CreateCar(color,wheel){this.color = color;this.wheel = wheel;this.showWheel = function(){//添加一个新方法alert(this.wheel);} }//还是采用同样的方法,生成实例:var cat1 = new CreateCar("红色","4");var cat2 = new CreateCar("蓝色","4");alert(cat1.showWheel == cat2.showWheel); //false

五、Prototype 原型

Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。 这意味着,我们可以把那些不变的属性和方法,直接定义在prototype对象上。__proto__是原型链,指向实例化的函数原型。

function CreateCar(color,wheel){//属性写构造函数里面this.color = color;this.wheel = wheel;}//方法写原型里面CreateCar.prototype.showWheel = function(){alert(this.wheel);}CreateCar.prototype.showName = function(){alert('车');}//生成实例。var cat1 = new CreateCar("红色","4");var cat2 = new CreateCar("蓝色","4");cat1.showName();//'车'//这时所有实例的showWheel属性和showName方法,其实都是同一个内存地址,指向prototype对象,因此就提高了运行效率。alert(cat1.showWheel == cat2.showWheel );//truealert(cat1.showName == cat2.showName );//trueconsole.log(cat1.__proto__ === CreateCar.prototype); //true

六、对象和函数的关系

对象是由函数构造出来的。

Object是Function 的一个实例。

Object.constructor == Function //true

函数是Function 的实例,但不是Object 的实例。

function fn(){}fn.constructor == Function //truefn.constructor == Object //false

{} 与 Object 的关系。

var obj = {};obj.constructor === Object //true

七、静态方法和静态属性

只属于类而不属于实例化对象

function foo(){this.show = function(){return this;}}foo.test = 123; //静态属性foo.say = function(){return this;}foo.say();var fn = new foo(); //实例化的新的对象,this指向这个新的对象,不能访问类的静态方法fn.say(); //Noname1.html:45 Uncaught TypeError: fn.say is not a functionconsole.log(foo.say() == fn.say());

八、对象继承

利用call()及for in继承 。

给对象的constructor.prototype添加方法属性,对象就会继承,如果要实现一个对象继承其他对象,采用如下方法。

//人类function Person(name,age){this.name = name;this.age = age;}Person.prototype.run = function(){console.log('跑路~')};Person.prototype.say = function(){console.log('说话~')};console.log(Person.prototype);//男人function Man(){this.sex = "男";}Man.prototype = Person.prototype;Man.prototype.yyy = function(){console.log('嘤嘤嘤');}//会发现Person的prototype也改变了,因为复杂对象的赋值操作是引用而不是赋值console.log(Person.prototype);//人类function Person(name,age){this.name = name;this.age = age;}Person.prototype.run = function(){console.log('跑路~')};Person.prototype.say = function(){console.log('说话~')};console.log(Person.prototype);//男人function Man(){this.sex = "男";}for(var key in Person.prototype){Man.prototype[key] = Person.prototype[key];console.log(key)}Man.prototype.yyy = function(){console.log('嘤嘤嘤');}console.log(Person.prototype);var xm = new Man();xm.yyy();采用中介function ClassA(name){this.name = name;}ClassA.prototype.say = function(){console.log(666);}//中继来做准备工作function Ready(){}//Ready.prototype = ClassA.prototype;//引用//需要来继承ClassAfunction ClassB(){}ClassB.prototype = new Ready();//new 返回了一个新对象 __proto__指向被实例化的构造函数的prototypeClassB.prototype.constructor = ClassB;console.log(ClassB.prototype);采用中介,使用call改变this指向function ClassA(name){this.name = name;}ClassA.prototype.showName = function(){console.log(this.name);}//中继来做准备工作function Ready(){}//Ready.prototype = ClassA.prototype;//引用//需要来继承ClassAfunction ClassB(name){ClassA.call(this,name);}ClassB.prototype = new Ready();//new 返回了一个新对象 __proto__指向被实例化的构造函数的prototypeClassB.prototype.constructor = ClassB;console.log(ClassB.prototype);var xiaoming = new ClassB('小明');xiaoming.showName();

九、多态

同一个方法,面对不同的对象有不同的表现形式就叫做多态。

var obj = {eat : function(_type){if(_type == '猫'){console.log('猫粮')}else if (_type == "狗") {console.log('狗粮')}else{console.log("吃饭");}}};obj.eat("狗");

十、hasOwnProperty

查看该属性是否在这个对象本身上,只有在自身属性上才会返回真,在原型链上会返回假。

function ClassA(){}ClassA.prototype.test = function(){console.log('test')}var a = new ClassA();a.test();console.log(a.hasOwnProperty('test')); //false

十一、描述符(修饰符)

描述符是对一个属性的特性的描述,defineProperty设置描述符(修饰符),value设置属性值,configurable是否允许修饰符被改变 默认为false,enumerable 是否可以被枚举 默认为false,writable 是否可以被 = 等号改变 默认为false。

var obj = {a : 1};var c = 666;Object.defineProperty(obj,'c',{//value : 233,//enumerable : false,//writable : true,//他的值能否改变//设置的时候调用set : function(n){//n 就是等号的右边的值c = c*n;},//获取的时候调用get : function(){return c;},configurable : true,//是否可以再次修改修饰符});

面向对象总结

所谓编程范式,指的是计算机编程的基本风格或典范模式。大致可以分为命令式编程和声明式编程。面向对象的程序设计是站在哲学的角度上,将人类思维融入到了程序里面的一种编程范式。描述对象的时候可以通过两个方法来进行描述。分别是对象的外观和功能。在程序中与之对应的就是属性和方法。JavaScript中的对象都是基于原型的,从一个原型对象中可以克隆出一个新的对象。在JavaScript中每个对象都有一个原型对象。可以通过__proto__属性来找到一个对象的原型对象。在其他语言中,对象从类中产生。而JavaScript中,通过构造函数来模拟其他语言中的类。类与对象的关系可以总结为:类是对对象的一种概括,而对象是类的一种具体实现。面向对象的三大特征:封装、继承、多态。封装是指对象的属性或者方法隐藏于内部,不暴露给外部。继承是指一个子类继承一个父类。在继承了父类之后,子类就拥有了父类的所有属性和方法。多态是指不同的对象可以拥有相同的方法,不过是以不同的方式来实现它。this的指向是根据使用的地方不同分为好几种情况,但是我们可以通过一些方式来修改this的指向。

执行上下文

先进后出,后进先出。
函数的执行上下文的生命周期

执行上下文的生命周期分为两个阶段:

创建阶段(进入执行上下文):函数被调用时,进入函数环境,为其创建一个函数上下文执行阶段(代码执行):执行函数中代码时,此时执行上下文进入执行状态。

创建阶段的操作
创建变量对象 函数环境会初始化创建 Arguments 对象,形式参数(并赋值)。普通函数声明(并赋值)局部变量声明,函数表达式声明(未赋值) 初始化作用域链确定this指向(this由调用者确定)确定作用域(词法环境决定,哪里声明定义,就在哪里确定)
执行阶段的操作
变量对象赋值 变量赋值函数表达式赋值 调用函数按顺序执行其他代码
执行上下文与作用域区别

作用域和执行上下文不是同一个概念。

执行全局代码时,会产生一个执行上下文环境,每次调用函数都会执行上下文环境。当函数调用完时,这个上下文环境以及其中的数据都会被消除(除了闭包),处于活动状态的执行上下文环境只有一个

而作用域在函数定义时就已经确定了,不是在函数调用时确定(区别于执行上下文环境,当然this也是上下文环境里的成分)

// 全局作用域let x = 100;function bar() { console.log(x);} // fn作用域function fn() { let x = 50; // bar作用域bar(); }fn(); // 100

作用域只是一个"地盘",其中没有变量。变量时哦通过作用域对应的执行上下文环境中的变量对象来实现的。所以作用域是静态观念的,而执行上下文环境是动态上的,两者并不一样。

变量对象

执行上下文抽象成为了一个对象,拥有个属性,分别是变量对象,作用域链以及this指向

变量对象里面所拥有的东西 Arguments对象确定形式参数,检查上下文中函数声明找到每一个函数声明 就在 variableObject 下用函数名创建一个属性,属性值就指向该函数在内存中的地址的一个引用。 确定当前上下文中的局部变量,如果遇到和函数名同名的变量,则会忽略该变量 通过例子来演示函数的这两个阶段以及变量对象是如何变化的。

const foo = function(i){var a = "Hello"; var b = function privateB(){}; function c(){} } foo(10);//首先在建立阶段fooExecutionContext = {variavleObject : {arguments : {0 : 10,length : 1}, // 确定arguments对象i : 10, // 确定形式参数c : pointer to function c(), // 确定函数的引用a : undefined, // 局部变量 初始值为 undefinedb : undefined // 局部变量 初始值为 undefined}, scopeChain : {}, this : {}}

14-2闭包(closure)

闭包:

一个函数中要嵌套一个内部函数,并且内部函数要访问外部函数的变量内部函数要被外部引用

function eat(){var food = '鸡翅';return function(){console.log(food);} }var look = eat(); look(); // 鸡翅look(); // 鸡翅//eat函数返回一个函数,并在这个内部函数中访问food这个局部变量。调用eat函数并将结果look,这个look指向了eat函数中的内部函数,然后调用它,最终输出food值

自由变量

自由变量可以理解成跨作用域的变量,比如子集作用域访问作用域的变量。

例:

var school = function () {var s1 = "小强"var s2 = "小王";var team = function (project) {console.log(s1 + s2 + project);}return team; } var team = school(); team("做电商项目"); // 小强小王做电商项目team("做微信项目"); // 小强小王做微信项目//变量s1和s2属于school函数的局部变量,并被内部哈桑农户team使用,同时该函数也被返回出去在外部,通过调用school得到了内部函数的引用,后面多次调用这个内部函数,仍然能够访问到s1和s2变量被team函数引用,即使创造它们的函数school执行完了,这些变量依然存在,这就是闭包

3:闭包的原理

因为作用域对象包含该上下文中的 VO/AO对象,还有scope对象,所以内部函数可以访问到函数的变量

4:闭包的优缺点

优点:

通过闭包可以让外部环境访问到函数内部的局部变量。通过闭包可以让局部变量持续保存下来,不随着它的上下文环境一起销毁。

缺点:局部变量本来应该在函数退出时被解除引用,但如果局部变量被封印在闭包形成的环境中,那么这个局部变量就一直能生存下去,然而闭包的确会使一些数据无法被及时销毁。

函数节流

目的:是为了防止一个函数短时间内被频繁的触发。原理:是将即将执行的函数用setTimeout()延迟一段时间执行。而函数节流的原理是让连续的函数执行,变为固定时间段间断的执行

1:使用时间戳

触发事件时,取出当前的时间戳,然后减去之前的时间戳(最一开始值设为0),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。

例:

function throttle(func, wait) {let context, args;let previous = 0;return function () {let now = +new Date(); context = this;args = arguments;if (now - previous > wait) {func.apply(context, args);previous = now; } }}

2:使用定时器

触发事件时,设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,知道定时器执行,然后执行函数,清空定时器,这样就可以设置一个定时器。

例:

function throttle(func, wait) {let timeout, args, context;let previous = 0;return function () {context = this;args = arguments; if (!timeout) {timeout = setTimeout(function () {timeout = null; func.apply(context, args)}, wait); } } }

14-3(分时函数)

timeChunk()函数接受3个参数。第一个参数是创建节点时需要用到的数据,第二个参数是封装了创建节点逻辑的函数,第三个参数表示每一批创建的节点数量

<body><script> const timeChunk = function (ary, fn, count) {let obj, t; let len = ary.length; const start = function () {for (let i = 0; i < Math.min(count || 1, ary.length); i++) {let obj = ary.shift();fn(obj); } }; return function () {t = setInterval(function () {//如果全部节点都已经被创建好 if (ary.length === 0) {return clearInterval(t); } start();}, 200); //分批执行的时间间隔,也可以用参数的形式传入}; }; //最后进行一些测试,假设我们有1000个好友的数据 // 然后利用timeChunk函数,每一批直往页面中创建8个节点const ary = []; for (let i = 1; i <= 1000; i++) {ary.push(i); } const renderFriendList = timeChunk(ary, function (n) {const div = document.createElement('div'); div.innerHTML = n; document.body.appendChild(div);}, 8); renderFriendList();</script></body>

可以看到1000条数据是分批创建添加的

##14-4(惰性函数)

在Web开发中,因为浏览器之间实现的差异,一些嗅探工作总是不可避免的,比如需要在各个浏览器中能够通用的事件绑定函数addEvent()

解决方式:

惰性载入函数方法

此时add Event()依然被声明为一个普通函数,在函数里依然有一些分支判断。但是在第一次进入条件分支之后,在函数内部会重写这个函数,重写之后的函数就是我们所期望的add Event()函数,在下一次进入时它不再存在条件分支

<body><div id="div1">点我绑定事件</div><script>const addEvent = function (elem, type, handler) {if (window.addEventListener) {addEvent = function (elem, type, handler) {elem.addEventListener(type, handler, false);} }if (window.attachEvent) {addEvent = function (elem, type, handler) {elem.attachEvent('on' + type, handler); } } addEvent(elem, type, handler); // 第一次绑定事件的时候需要手动调用以下}; const div = document.getElementById('div1'); addEvent(div, 'click', function () {alert(1);});addEvent(div, 'click', function () {alert(2);}); </script> </body>

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。