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

3 篇文章 0 订阅
1 篇文章 0 订阅

面向对象


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

面向对象

一、面向对象基本特征
  1. 封装:也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
  2. 继承:通过继承创建的新类称为“子类”或“派生类”。继承的过程,就是从一般到特殊的过程。
  3. 多态:对象的多功能,多方法,一个方法多种表现形式。
  4. Javascript是一种基于对象(object-based)的语言。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类)—–es6以前是这样的。所以es5只有使用函数模拟的面向对象。
二、对象实例化方式
  1. 原始模式:这样的写法有两个缺点,一是如果多生成几个(100个!)实例,写起来就非常麻烦;二是实例与原型之间,没有任何办法,可以看出没有什么联系。
var Car = {
    color: 'red',//车的颜色
    wheel: 4,//车轮数量
}
var Car2 = {
    color: 'blue',
    wheel: 4,
}
alert(Car.color);//red
  1. 原始模式的改进:通过写一个函数,解决代码重复的问题。
function createCar(color,wheel) {
    return {
        color:color,
        wheel:wheel
    }
}
//然后生成实例对象,就等于是在调用函数:
var cat1 = createCar("红色","4");
var cat2 = createCar("蓝色","4");

alert(cat1.color);//红色
  1. 工厂模式
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);//红色
  1. 构造函数模式:为了解决从原型对象生成实例的问题,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);//红色
三、构造函数注意事项
  1. 此时CreateCar称之为构造函数,也可以称之类,构造函数就是类 。
  2. cat1,cat2均为CreateCar的实例对象。
  3. CreateCar构造函数中this指向CreateCar实例对象即 new CreateCar( )出来的对象。
  4. 必须带new 。
  5. 构造函数首字母大写,这是规范,官方都遵循这一个规范,如Number() Array()。
  6. contructor:这时cat1和cat2会自动含有一个constructor属性,指向它们的构造函数,即CreateCar。
alert(cat1.constructor == CreateCar); //true
alert(cat2.constructor == CreateCar); //true
  1. 每定义一个函数,这个函数就有一个 prototype 的属性{},proto 指向被实例化的构造函数的prototype,prototype默认带constructor属性,constructor指向构造函数。
    instanceof 运算符:object instanceof constructor运算符,验证构造函数与实例对象之间的关系。
alert(cat1 instanceof CreateCar ); //true
alert(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 );//true
alert(cat1.showName == cat2.showName );//true
console.log(cat1.__proto__ === CreateCar.prototype); //true
六、对象和函数的关系

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

Object是Function 的一个实例。

Object.constructor  == Function  //true

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

function fn(){}
fn.constructor  == Function  //true
fn.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 function
console.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;//引用

//需要来继承ClassA
function ClassB(){}
ClassB.prototype = new Ready();//new 返回了一个新对象 __proto__指向被实例化的构造函数的prototype
ClassB.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;//引用

//需要来继承ClassA
function ClassB(name){
    ClassA.call(this,name);
}
ClassB.prototype = new Ready();//new 返回了一个新对象 __proto__指向被实例化的构造函数的prototype
ClassB.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,//是否可以再次修改修饰符
});

面向对象总结

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

执行上下文

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

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

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

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

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

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

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

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

变量对象

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

  • 变量对象里面所拥有的东西
  1. Arguments对象
  2. 确定形式参数,检查上下文中函数声明
  3. 找到每一个函数声明
  • 就在 variableObject 下用函数名创建一个属性,属性值就指向该函数在内存中的地址的一个引用。
  1. 确定当前上下文中的局部变量,如果遇到和函数名同名的变量,则会忽略该变量
  • 通过例子来演示函数的这两个阶段以及变量对象是如何变化的。
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, // 局部变量 初始值为 undefined
b : 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>
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值