面向对象编程

面向对象编程 (Object Oriented Programming)
将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟

  • 对象需要一个模板,来表示某一类实物的共同特征。所谓“类”就是对象的模板,对象就是“类”的实例。
  • JavaScript 语言使用构造函数(constructor)作为对象的模板。

构造函数

构造函数就是一个普通的函数
构造函数名字的第一个字母通常大写

var Vehicle = function () {
  this.price = 1000;
};

构造函数的特点

  • 函数体内部使用了this关键字,代表了所要生成的对象实例。
  • 生成对象的时候,必须使用new命令。

new 命令

new命令的作用,就是执行构造函数,返回一个实例对象。

var v = new Vehicle(); //推荐带括号的写法

忘加new的话会使构造函数当成普通函数执行,从而出错。
解决办法一:函数内部使用严格模式

function Fubar(foo, bar){
  'use strict';
  this._foo = foo;
  this._bar = bar;
}

Fubar()
// TypeError: Cannot set property '_foo' of undefined

解决办法二:构造函数内部判断是否使用new命令,如果发现没有使用,则直接返回一个实例对象。

function Fubar(foo, bar) {
  if (!(this instanceof Fubar)) {
    return new Fubar(foo, bar);
  }

  this._foo = foo;
  this._bar = bar;
}

Fubar(1, 2)._foo // 1
(new Fubar(1, 2))._foo // 1

new 命令的原理:

  1. 创建一个空对象,作为将要返回的对象实例。
  2. 将这个空对象的原型,指向构造函数的prototype属性。
  3. 将这个空对象赋值给函数内部的this关键字。
  4. 开始执行构造函数内部的代码。

如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象。

new.target
函数内部可以使用new.target属性。如果当前函数是new命令调用,new.target指向当前函数,否则为undefined。
使用这个属性,可以判断函数调用的时候,是否使用new命令。

function f() {
  if (!new.target) {
    throw new Error('请使用 new 命令调用!');
  }
  // ...
}

f() // Uncaught Error: 请使用 new 命令调用!

this 关键字

this有一个共同点:它总是返回一个对象。
换句话说,this就是属性或方法“当前”所在的对象。
this就是函数运行时所在的对象(环境)
this的指向是动态的

使用场合:

  • 全局环境使用this,它指的就是顶层对象window。
  • 构造函数中的this,指的是实例对象。
  • 如果对象的方法里面包含this,this的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this的指向。
// 注意
var obj ={
  foo: function () {
    console.log(this);
  }
};
obj.foo() // obj
(false || obj.foo)() // window
/*解释:JavaScript 引擎内部,obj和obj.foo储存在两个内存地址,称为地址一和地址二。
obj.foo()这样调用时,是从地址一调用地址二,因此地址二的运行环境是地址一,this指向
obj。obj.foo是直接取出地址二进行调用,这样的话,运行环境就不是地址一了*/

使用注意点:

  • 避免多层 this

    解决:

    1. 使用一个变量固定this的值,然后内层函数调用这个变量。
    2. 严格模式下,如果函数内部的this指向顶层对象,就会报错。
  • 避免数组处理方法中的 this

  • 避免回调函数中的 this

绑定 this 的方法

使用call、apply、bind这三个方法,来切换/固定this的指向。

  • Function.prototype.call()

    func.call(thisValue, arg1, arg2, ...)
    call的第一个参数就是this所要指向的那个对象,后面的参数则是函数调用时所需的参数。

// call方法的参数,指定函数执行时所在的作用域
// 参数,应该是一个对象。如果参数为空、null和undefined,则默认传入全局对象。
var obj = {};
var f = function () {
  return this;
};
f() === window // true
f.call(obj) === obj // true
function add(a, b) {
  return a + b;
}
add.call(this, 1, 2) // 3
  • Function.prototype.apply()

apply方法的作用与call方法类似,唯一的区别就是,它接收一个数组作为函数执行时的参数。
func.apply(thisValue, [arg1, arg2, ...])
利用这一点,可以做一些有趣的应用。

// 找出数组最大元素
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15

// 将数组的空元素变为undefined
Array.apply(null,['a',' ','b']) // [ 'a', undefined, 'b' ]
Array.from(['a',,'b'])// [ "a", undefined, "b" ]

// 转换类似数组的对象,利用数组对象的slice方法
Array.prototype.slice.apply({0: 1, length: 1}) // [1]
  • Function.prototype.bind()

    bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。
    如果bind方法的第一个参数是null或undefined,等于将this绑定到全局对象,函数运行时this指向顶层对象(浏览器为window)。

var counter = {
  count: 0,
  inc: function () {
    this.count++;
  }
};
var obj = {
  count: 100
};
var func = counter.inc.bind(obj);
func();
obj.count // 101

多个参数:
多的参数与原函数的参数依次绑定

var add = function (x, y) {
  return x * this.m + y * this.n;
}

var obj = {
  m: 2,
  n: 2
};

var newAdd = add.bind(obj, 5);//第一个参数x绑定成5
//传入第二个参数y
newAdd(6) // 22

注意:

  • forEach方法的回调函数内部的this是指向全局对象的,因而如果有使用this的话则要.bind(this)

prototype 对象

每个函数都有一个prototype属性,指向一个对象。
所有对象都有自己的原型对象(prototype)。
原型链的尽头就是null。
prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。
修改原型对象(把一个新的对象赋值给原型对象)时,一般要同时修改constructor属性的指向。

  • instanceof 运算符
    返回一个布尔值,表示对象是否为某个构造函数的实例。
var v = new Vehicle();
v instanceof Vehicle // true

instanceof的原理是检查右边构造函数的prototype属性,是否在左边对象的原型链上,检查整个原型链。
instanceof运算符只能用于对象,不适用原始类型的值。
对于undefined和null,instanceOf运算符总是返回false。
有一种特殊情况,就是左边对象的原型链上,只有null对象。这时,instanceof判断会失真。

// 小应用,检查调用构造函数时,是否忘了加new命令
function Fubar (foo, bar) {
  if (this instanceof Fubar) {
    this._foo = foo;
    this._bar = bar;
  } else {
    return new Fubar(foo, bar);
  }
}

Object 对象的相关方法

  • Object.getPrototypeOf()

    返回参数对象的原型。这是获取原型对象的最佳方法。

  • Object.setPrototypeOf()

    为参数对象设置原型,返回该参数对象。
    接受两个参数,第一个是现有对象,第二个是原型对象。

var a = {};
var b = {x: 1};
Object.setPrototypeOf(a, b);
a.x // 1
  • Object.create()

    从一个实例对象,生成另一个实例对象。
    接受一个对象作为参数,然后以它为原型,返回一个实例对象。

// 原型对象
var A = {
  print: function () {
    console.log('hello');
  }
};

// 实例对象
var B = Object.create(A);

第二个参数是一个属性描述对象,它所描述的对象属性,会添加到实例对象,作为该对象自身的属性。

  • Object.prototype.isPrototypeOf()

    用来判断该对象是否为参数对象的原型,返回布尔值

  • Object.getOwnPropertyNames()

    返回一个数组,成员是参数对象本身的所有属性(不管是否可以遍历)的键名,不包含继承的属性键名。
    只获取那些可以遍历的属性,使用Object.keys方法。

  • Object.prototype.hasOwnProperty()

    判断某个属性定义在对象自身,还是定义在原型链上。
    hasOwnProperty方法是 JavaScript 之中唯一一个处理对象属性时,不会遍历原型链的方法。

对象的拷贝

如果要拷贝一个对象,需要做到下面两件事情。

  • 确保拷贝后的对象,与原对象具有同样的原型。
  • 确保拷贝后的对象,与原对象具有同样的实例属性。
function copyObject(orig) {
  return Object.create(
    Object.getPrototypeOf(orig),
    Object.getOwnPropertyDescriptors(orig)
  );
}

面向对象编程的模式

  • 构造函数的继承

    一个构造函数继承另一个构造函数,共两步:

    • 在子类的构造函数中,调用父类的构造函数。
// Sub是子类的构造函数,this是子类的实例
function Sub(value) {
  Super.call(this); // 让子类实例具有父类实例的属性
  this.prop = value;
}
  • 让子类的原型指向父类的原型
Sub.prototype = Object.create(Super.prototype);
/* 这里,如果直接把Super.prototype赋值给Sub.prototype,
则对Sub.prototype的操作,会连父类的原型Super.prototype一起修改掉。*/
Sub.prototype.constructor = Sub;

只需要单个方法时,简洁写法:

ClassB.prototype.print = function() {
  ClassA.prototype.print.call(this);
  // some code
}
  • 模块

    模块是实现特定功能的一组属性和方法的封装。

    基本写法:

var module1 = (function () {
 var _count = 0;
 var m1 = function () {
   //...
 };
 var m2 = function () {
  //...
 };
 return {
  m1 : m1,
  m2 : m2
 };
})();

模块的放大模式:
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用“放大模式”(augmentation)。
例如:为module1模块添加了一个新方法m3()。

var module1 = (function (mod){
 mod.m3 = function () {
  //...
 };
 return mod;
})(module1);

如果上面的添加操作先于module1执行操作,可能加载一个不存在空对象,采用”宽放大模式”(Loose augmentation),允许“立即执行函数”的参数是空对象。

ar module1 = ( function (mod){
 //...
 return mod;
})(window.module1 || {});

为了在模块内部调用全局变量,必须显式地将其他变量输入模块。

var module1 = (function ($, YAHOO) {
 //...
})(jQuery, YAHOO);

上面的module1模块需要使用jQuery库和YUI库,就把这两个库(其实是两个模块)当作参数输入module1。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。

补充:

  • __proto__和prototype的区别?

__proto__是对象的属性,prototype是函数的属性

var 对象 = new 函数();      //函数可以是Number/String/Boolean/Object
对象.__proto__ === 函数.prototype    //true
  • '1'.__proto__是什么?
// ’1’会临时转化为String对象,所有对象都有__proto__属性。
'1'.__proto__ === String.prototype    //true

Object.prototype.__proto__ === null; //true
1.toString()     //语法错误,不加引号JS会把.当作小数点
1..toString()    //'1',第一个.当作小数点,第二个为点操作符
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值