JavaScript面向对象核心知识概括

1、面向对象

1.1 概念

  1. 面向对象就是使用对象。面向对象开发就是使用对象开发。
  2. 面向过程就是用过程的方式进行开发。面向对象是对面向过程的封装。

1.2 三大特性

抽象性
所谓的抽象性就是:若是须要一个对象描述数据,须要抽取这个对象的核心数据html

  1. 提出须要的核心属性和方法
  2. 不在特定的环境下没法明确对象的具体意义

封装性
对象是将数据与功能组合到一块儿,即封装程序员

  1. JS对象就是键值对的集合,键值若是是数据(基本数据、符合数据、空数据)就称为属性,若是键值是函数那么就称为方法
  2. 对象就是将属性与方法封装起来
  3. 方法是将过程封装起来

继承性
所谓继承性就是本身没有可是别人有,拿过来成为本身的,就是继承,继承是实现复用的一种手段算法

  • 在Java等语言中继承知足一个class的规则,类是一个class,他规定了一个对象有什么属性和方法。
  • 在这些语言中继承是class之间的继承,一个class继承另外一个class,那么该class就有了另外一个class的成员,那么由该class建立出来的对象就同时具备两个class的成员。

在JS中没有明确的继承语法(ES6提供了class extend语法),通常都是按照继承的理念实现对象的成员扩充实现继承,所以JS中实现继承的方法很是对多。编程

传统继承基于类,JS继承基于对象数组

一个简单的继承模式:混入(mix)浏览器

function mix ( o1, o2 ) {
    for ( var k in o2 ) {
        o1[ k ] = o2[ k ];
    }
}

1.3 关于面向对象的一些其余概念

类class:在JS中就是构造函数缓存

  • 在传统的面向对象语言中,使用一个叫类的东西定义模板,而后使用模板建立对象。
  • 在构造方法中也具备相似的功能,所以也称其为类

实例(instance)与对象(object)闭包

  • 实例通常是指某一个构造函数建立出来的对象,咱们称为XXXX 构造函数的实例
  • 实例就是对象。对象是一个泛称
  • 实例与对象是一个近义词

键值对与属性和方法app

  • 在JS中键值对的集合称为对象
  • 若是值为数据(非函数),就称该键值对为属性
  • 若是值为函数(方法),就称该键值对为方法method

父类与子类(基类和派生类)dom

  • 传统的面向对象语言中使用类来实现继承那么就有父类、子类的概念
  • 父类又称为基类,子类又称为派生类
  • 在JS中没有类的概念,在JS中经常称为父对象,子对象,基对象,派生对象。

2、构造函数

2.1 构造函数是干什么用的

  1. 初始化数据的
  2. 在JS中给对象添加属性用的,初始化属性值用的

2.2 建立对象的过程

  1. 代码:var p = new Person();
  2. 首先运算符new建立了一个对象,相似于{},是一个没有任何(自定义)成员的对象。

    • 使用new 建立对象,那么对象的类型就是建立他的构造函数名
    • 使用{}不管如何都是Object类型,至关于new Object
  3. 而后调用构造函数,为其初始化成员

    • 构造函数在调用的一开始,有一个赋值操做,即this = 刚刚建立出来的对象。
    • 所以在构造函数中this表示刚刚建立出来的对象。
  4. 在构造函数中 利用 对象的动态特性 为其对象添加成员。

3、做用域

3.1 什么是做用域

域表示的就是范围,即做用域,就是一个名字在什么地方可使用,何时不能使用。
简单的说,做用域是针对变量的,好比咱们建立一个函数 a1,函数里面又包了一个子函数 a2

// 全局做用域
functiona a1() {
    // a1做用域
    function a2() {
        // a2做用域
    }
}

此时就存 在三个做用域:全局做用域,a1 做用域,a2 做用域;即全局做用域包含了 a1 的做用域,a2 的做用域包含了 a1 的做用域。

a2 在查找变量的时候会先从自身的做用域区查找,找不到再到上一级 a1 的做用域查找,若是还没找到就
到全局做用域区查找,这样就造成了一个做用域链

3.2 JS中词法做用域的规则

  1. 函数容许访问函数外部的数据
  2. 整个代码结构中只有函数能够限定做用域
  3. 做用规则首先使用提高规则分析
  4. 若是当前做用域中有了名字了,就不考虑外面的名字

3.3 属性搜索原则

  • 所谓的属性搜索原则,就是对象在访问属性或方法的时候,首先在当前对象中查找
  • 若是当前对象中存储着属性或方法,中止查找,直接使用该属性或方法
  • 若是当前对象没有该成员,那么再在其原型对象中查找
  • 若是原型对象中含有该成员,那么中止查找,直接使用
  • 若是原型中尚未,就到原型的原型中查找
  • 如此往复,直到Object.protitype尚未,那么就返回undefined
  • 若是是调用方法就报错,该xxx不是一个函数

4、闭包

4.1 说说你对闭包的理解

实用闭包主要是为了设计私有方法和变量。闭包的优势是能够避免全局变量的污染;缺点是闭包会常驻内存,增长内存使用量,使用不当很容易形成内存泄露。在JavaScript中,函数即闭包,只有函数才能产生做用域。

闭包有3个特性:

  1. 函数嵌套函数
  2. 在函数内部能够引用外部的参数和变量
  3. 参数和变量不会以垃圾回收机制回收

4.2 闭包有什么用(特性)

闭包的做用,就是保存本身私有的变量,经过提供的接口(方法)给外部使用,但外部不能直接访问该变量。
经过使用闭包,咱们能够作不少事情,好比模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提高代码的执行效率。利用闭包能够实现以下需求:

  1. 匿名自执行函数

一个匿名的函数,并当即执行它,因为外部没法引用它内部的变量,所以在执行完后很快就会被释放,关键是这种机制不会污染全局对象。

  1. 缓存

闭包正是能够作到这一点,由于它不会释放外部的引用,从而函数内部的值能够得以保留。

  1. 实现封装
  2. 模拟面向对象的代码风格

4.3 闭包的基本模型

对象模式
函数内部定义个一个对象,对象中绑定多个函数(方法),返回对象,利用对象的方法访问函数内的数据

function createPerson() {
    var __name__ = "";
    return {
        getName: function () {
            return __name__;
        },
        setName: function( value ) {
            // 若是不姓张就报错
            if ( value.charAt(0) === '张' ) {
                __name__ = value;
            } else {
                throw new Error( '姓氏不对,不能取名' );
            }
        }
    }
}
var p = createPerson();
p.set_Name( '张三丰' );
console.log( p.get_Name() );
p.set_Name( '张王富贵' );
console.log( p.get_Name() );

函数模式
函数内部定义一个新函数,返回新函数,用新函数得到函数内的数据

function foo() {
    var num = Math.random();
    function func() {
        return mun;
    }
    return func;
}
var f = foo();
// f 能够直接访问这个 num
var res1 = f();
var res2 = f();

沙箱模式
沙箱模式就是一个自调用函数,代码写到函数中同样会执行,可是不会与外界有任何的影响,好比jQuery

(function () {
   var jQuery = function () { // 全部的算法 }
   // .... // .... jQuery.each = function () {}
   window.jQuery = window.$ = jQuery;
})();
$.each( ... )

4.4 闭包的性能问题

js 垃圾回收机制,也就是当一个函数被执行完后,其做用域会被收回,若是造成了闭包,执行完后其做用域就不会被收回。

函数执行须要内存,那么函数中定义的变量,会在函数执行结束后自动回收,凡是由于闭包结构的,被引出的数据,若是还有变量引用这些数据的话,那么这些数据就不会被回收。所以在使用闭包的时候若是不使用某些数据了,必定要赋值一个null

var f = (function () {
    var num = 123;
    return function () {
        return num;
    };
})();
// f 引用着函数,函数引用着变量num
// 所以在不使用该数据的时候,最好写上
f = null;

5、原型

5.1 什么是原型

一句话说明什么是原型:原型能存储咱们的方法,构造函数建立出来的实例对象可以引用原型中的方法。

JS中一切皆对象,而每一个对象都有一个原型(Object除外),这个原型,大概就像Java中的父类,因此,基本上你能够认为原型就是这个对象的父对象,即每个对象(Object除外)内部都保存了它本身的父对象,这个父对象就是原型。通常建立的对象若是没有特别指定原型,那么它的原型就是Object(这就很相似Java中全部的类默认继承自Object类)。

ES6经过引入class ,extends等关键字,以一种语法糖的形式把构造函数包装成类的概念,更便于你们理解。是但愿开发者再也不花精力去关注原型以及原型链,也充分说明原型的设计意图和类是同样的。

5.2 查看对象的原型

当对象被建立以后,查看它们的原型的方法不止一种,之前通常使用对象的__proto__属性,ES6推出后,推荐用Object.getPrototypeOf()方法来获取对象的原型

function A(){
    this.name='lala';
}
var a=new A();
console.log(a.__proto__) 
//输出:Object {}
 
//推荐使用这种方式获取对象的原型
console.log(Object.getPrototypeOf(a)) 
//输出:Object {}

不管对象是如何建立的,默认原型都是Object,在这里须要说起的比较特殊的一点就是,经过构造函数来建立对象,函数A自己也是一个对象,而A有两个指向表示原型的属性,分别是__proto__prototype,并且两个属性并不相同

function A(){
    this.name='lala';
}
var a=new A();
console.log(A.prototype) 
//输出:Object {}
 
console.log(A.__proto__) 
//输出:function () {}
console.log(Object.getPrototypeOf(A))
//输出:function () {}

函数的的prototype属性只有在看成构造函数建立的时候,把自身的prototype属性值赋给对象的原型。而实际上,做为函数自己,它的原型应该是function对象,而后function对象的原型才是Object

总之,建议使用ES6推荐的查看原型和设置原型的方法。

5.3 原型的用法

其实原型和类的继承的用法是一致的:当你想用某个对象的属性时,将当前对象的原型指向该对象,你就拥有了该对象的使用权了。

function A(){
    this.name='world ';
}
function B(){
    this.bb="hello"
}
var a=new A();
var b=new B();

//将b设置为a的原型,此处有一个问题,即a的constructor也指向了B构造函数,可能须要纠正 
Object.setPrototypeOf(a,b);
a.constructor=A;
console.log(a.bb);    //hello

若是使用ES6来作的话则简单许多,甚至不涉及到prototype这个属性

class B{
     constructor(){
         this.bb='hello'
     }
}
class A  extends B{
     constructor(){
        super();
        this.name='world';
     }
}
 
var a=new A();
console.log(a.bb+" "+a.name);    //hello world
console.log(typeof(A))                //"function"

怎么样?是否是已经彻底看不到原型的影子了?活脱脱就是类继承,可是你也看获得实际上类A 的类型是function,因此说,本质上class在JS中是一种语法糖,JS继承的本质依然是原型,不过,ES6引入classextends 来掩盖原型的概念也是一个很友好的举动,对于长期学习那些类继承为基础的面对对象编程语言的程序员而言。

个人建议是,尽量理解原型,尽量用class这种语法糖。

好了,问本身两个问题:

  1. 为何要使用原型?——提升函数的复用性。
  2. 为何属性不放在原型上而方法要放在原型上?

    • 利用对象的动态特性:构造函数.prototype.xxxx = vvv
    • 利用直接替换
    Student.prototype = {
    sayHello : function(){},
    study : function(){}
    };

5.4 原型链

什么是原型链?
凡是对象就有原型,那么原型又是对象,所以凡是给定一个对象,那么就能够找到他的原型,原型还有原型,那么如此下去,就构成一个对象的序列,称该结构为原型链。

每一个实例对象都有一个 __proto_属性,该属性指向它原型对象,这个实例对象 的构造函数有一个原型属性 prototype,与实例的 __proto__属性指向同一个对象。当一个对象在查找一个属性的时, 自身没有就会根据 __proto__ 向它的原型进行查找,若是都没有,则向它的原型的原型继续查找,直到查到 Object.prototype._proto_null,这样也就造成了 原型链

这个概念其实也变得比较简单,能够类比类的继承链条,即每一个对象的原型往上追溯,一直到Object为止,这组成了一个链条,将其中的对象串联起来,当查找当前对象的属性时,若是没找到,就会沿着这个链条去查找,一直到Object,若是还没发现,就会报undefined

原型链的结构
凡是使用构造函数,建立出对象,而且没有利用赋值的方式修改原型,就说该对象保留默认的原型链。
默认原型链结构是什么样子呢?

function Person(){}
var p = new Person();
//p 具备默认的原型链

默认的原型链结构就是:当前对象 -> 构造函数.prototype -> Object.prototype -> null

在实现继承的时候,有时候会利用替换原型链结构的方式实现原型继承,那么原型链结构就会发生改变

function DunizbCollection(){}
DunizbCollection.prototype = [];
var arr = new DunizbCollection();

此时arr对象的原型链结构被指向了数组对象的原型链结构了:arr -> [] -> Array.prototype -> Object.prototype -> null

用图形表示对象的原型链结构
以以下代码为例绘制原型链结构

function Person(){}
var p = new Person();

原型链结构图为:
原型链结构图

使用原型须要注意两点:

  1. 原型继承链条不要太长,不然会出现效率问题。
  2. 指定原型时,注意constructor也会改变。

6、继承

实现继承有两种常见方式:

6.1 混合式继承

最简单的继承就是将别的对象的属性强加到我身上,那么我就有这个成员了。
混合式继承的简单描述:

function Person() {};
Person.prototype.extend = function ( o ) {
     for ( var k in o ) {
          this[ k ] = o[ k ];
     }
};
Person.prototype.extend({
      run: function () { console.log( '我能跑了' ); },
      eat: function () { console.log( '我能够吃了' ); },
      sayHello: function () { console.log( '我吃饱了' ); }
});

6.2 原型继承

利用原型也能够实现继承,不须要在我身上添加任何成员,只要原型有了我就有了。

6.3 借用构造函数继承

这种技术的基本思想至关简单,即在子类型构造函数的内部调用超类型构造函数,而函数只不过是在特定环境中执行代码的对象,所以经过使用apply()call()方法也能够在(未来)新建立的对象上执行构造函数

function Person ( name, age, gender ) {
        this.name = name;
        this.age = age;
        this.gender = gender;
}
// 须要提供一个 Student 的构造函数建立学生对象
// 学生也应该有 name, age, gender, 同时还须要有 course 课程
function Student ( name, age, gender, course ) {
        Person.call( this, name, age, gender );
        this.course = course;
}

在《JavaScript高级程序设计(第三版)》中详细介绍了继承的6种方式

7、函数的四种调用模式

7.1 函数模式

就是一个简单的函数调用。函数名的前面没有任何引导内容。

function foo () {}
var func = function () {};
...
foo();
func();
(function () {} )();

this 的含义:在函数中 this 表示全局对象,在浏览器中式 window

7.2 方法模式

方法必定式依附与一个对象,将函数赋值给对象的一个属性,那么就成为了方法。

function f() {
    this.method = function () {};
}
var o = {
    method: function () {}
}

this 的含义:这个依附的对象

7.3 构造器调用模式

建立对象的时候构造函数作了什么?因为构造函数只是给 this 添加成员,没有作其余事情。而方法也能够完成这个操做,就是 this 而言,构造函数与方法没有本质的区别。

特征:

  1. 使用 new 关键字,来引导构造函数。
  2. 构造函数中的 this 与方法中的同样,表示对象,可是构造函数中的对象是刚刚建立出来的对象
  3. 构造函数中不须要 return ,就会默认的 return this

    • 若是手动添加return ,就至关于 return this
    • 若是手动的添加 return 基本类型,无效,仍是保留原来 返回 this
    • 若是手动添加的 return null,或 return undefined ,无效
    • 若是手动添加 return 对象类型,那么原来建立的 this 就会被丢掉,返回的是 return 后面的对象

7.4 上下文调用模式

上下文就是环境。就是本身定义设置 this 的含义。

语法

  1. 函数名.apply( 对象, [ 参数 ] );
  2. 函数名.call( 对象, 参数 );

描述

  1. 函数名就是表示函数自己,使用函数进行调用的时候默认 this 是全局变量
  2. 函数名也能够是方法提供,使用方法调用的时候,this 是指向当前对象
  3. 使用 apply 进行调用后,不管是函数仍是方法都无效了,咱们的 this ,由 apply 的第一个参数决定

参数问题
不管是 call 仍是 apply 在没有后面的参数的状况下(函数无参数,方法五参数)是彻底一致的

function foo(){
    console.log( this );
}
foo.apply( obj );
foo.call( obj );

第一个参数的使用也是有规则的:

  • 若是传入的是一个对象,那么就至关于设置该函数中的 this 为参数
  • 若是不传入参数,或传入 nullundefined 等,那么至关于 this 默认为 window
foo();
foo.apply();
foo.apply( null );
  • 若是传入的是基本类型,那么 this 就是基本类型对应的包装类型的引用

在使用上下文调用的时候,原函数(方法)可能会带有参数,那么这个参数再上下文调用中使用 第二个(第 n 个)参数来表示

function foo( num ) {
    console.log( num );
}
foo.apply( null, [ 123 ] );
// 等价于
foo( 123 );

参考资料

相关文章
相关标签/搜索