面向对象
面向对象编程
(Object Oriented Programming,缩写OOP):将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟,其有三个特征:封装、继承和多态
对象
- 对象是单个实物的抽象
- 对象是一个容器,封装了属性(property)和方法(method)
构造函数
就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构,为了与普通函数区别,构造函数名字的第一个字母通常大写
有两个特点:
- 函数体内部使用了
this关键字,代表了所要生成的对象实例 - 生成对象的时候,必须使用
new命令
new命令运行的基本原理
- 创建一个空对象,作为将要返回的实例对象
- 将这个空对象的原型,指向构造函数的
prototype属性 - 将空对象赋值给函数内部的
this关键字 - 开始执行构造函数内部的代码
提示:如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;否则,就会不管return语句,返回this对象
new命令的内部运行代码
function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {
// 将 arguments 对象转为数组
var args = [].slice.call(arguments);
// 取出构造函数
var constructor = args.shift();
// 创建一个空对象,继承构造函数的 prototype 属性
var context = Object.create(constructor.prototype);
// 执行构造函数
var result = constructor.apply(context, args);
// 如果返回结果是对象,就直接返回,否则返回 context 对象
return (typeof result === 'object' && result != null) ? result : context;
}
// 实例
function Person(name, age) {
this.name = name;
this.age = age;
}
var actor = _new(Person, '张三', 28);
使用new.target属性可以判断函数调用的时候,是否使用new命令
Object.create() 创建实例对象
构造函数作为模板,可以生成实例对象。但是,有时拿不到构造函数,只能拿到一个现有的对象。我们希望以这个现有的对象作为模板,生成新的实例对象,这时就可以使用Object.create()方法,继承了前者的属性和方法
this关键字
简单说,this就是属性或方法“当前”所在的对象
- 它总是返回一个对象
- 由于对象的属性可以赋给另一个对象,所以属性所在的当前对象是可变的,即
this的指向是可变的 - 它的设计目的就是在函数体内部,指代函数当前的运行环境
使用场合
- 事件调用环境——谁触发事件,指向谁
- 全局环境——window(浏览器环境)、module.exports(node环境),严格模式都是undefined
- 构造函数——构造函数中的
this,指的是实例对象 - 对象的方法——如果对象的方法里面包含
this,this的指向就是方法运行时所在的对象。该方法赋值给另一个对象,就会改变this的指向 - 在箭头函数中——指的是函数定义生效时所在的对象,而不是使用时所在的对象
this的五种绑定规则(除了箭头函数,this总是指向调用该函数的对象)
-
默认绑定(严格/非严格模式):指向全局对象或者
undefined -
隐式绑定:上下文对象
-
显示绑定:使用
call、apply、bind等 -
new绑定 -
箭头函数绑定:
- 箭头函数不绑定`this`,箭头函数中的this相当于普通变量,指向外层作用域,对象没有单独的作用域 - 箭头函数的`this`寻值行为与普通变量相同,在作用域中逐级寻找 - 箭头函数的`this`无法通过`bind`,`call`,`apply`来直接修改(可以间接修改) - 改变作用域中this的指向可以改变箭头函数的`this` - `eg. function closure(){()=>{//code }}`,在此例中,我们通过改变封包环境`closure.bind(another)()`,来改变箭头函数`this`的指向
使用注意点
- 避免多层
this,由于this的指向是不确定的,所以切勿在函数中包含多层的this,使用一个变量固定this的值,然后内层函数调用这个变量,是非常常见的做法 - 避免数组处理方法中的
this,数组的map和foreach方法,允许提供一个函数作为参数。这个函数内部不应该使用this,解决这个问题的一种方法,就是前面提到的,使用中间变量固定this;另一种方法是将this当作foreach方法的第二个参数,固定它的运行环境 - 避免回调函数中的
this,回调函数中的this往往会改变指向,最好避免使用,为了解决这个问题,可以采用下面的一些方法对this进行绑定,也就是使得this固定指向某个对象,减少不确定性
绑定 this的方法
this的动态切换,固然为 JavaScript 创造了巨大的灵活性,但也使得编程变得困难和模糊。有时,需要把this固定下来,避免出现意想不到的情况。JavaScript 提供了call、apply、bind这三个方法,来切换/固定this的指向
-
Function.prototype.call():函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数,call方法的参数,应该是一个对象。如果参数为空、null和undefined,则默认传入全局对象,如果call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入call方法,形式如func.call(thisValue, arg1, arg2, ...) -
Function.prototype.apply():apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下:func.apply(thisValue, [arg1, arg2, ...]),利用这一点,可以做一些有趣的应用:- 找出数组最大元素
- 将数组的空元素变为
undefined - 转换类似数组的对象
- 绑定回调函数的对象
-
Function.prototype.bind():bind()方法用于将函数体内的this绑定到某个对象,然后返回一个新函数,bind()方法有一些使用注意点:- 每一次返回一个新函数
- 结合回调函数使用
- 结合
call()方法使用
原型与原型链
prototype属性:每个函数都有一个prototype属性,指向一个对象,原型对象的所有属性和方法,都能被实例对象共享
function f(){}
f.prototype // f {}
typeof f.prototype // 'object'
函数默认具有prototype属性,对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象
原型链:JavaScript 规定,所有对象都有自己的原型对象(prototype)。一方面,任何一个对象,都可以充当其他对象的原型;另一方面,由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”(prototype chain),对象到原型,再到原型的原型...
如果一层层地上溯,所有对象的原型最终都可以上溯到Object.prototype,即Object构造函数的prototype属性。也就是说,所有对象都继承了Object.prototype的属性。这就是所有对象都有valueOf和toString方法的原因,因为这是从Object.prototype继承的
那么,Object.prototype对象有没有它的原型呢?回答是Object.prototype的原型是null。null没有任何属性和方法,也没有自己的原型。因此,原型链的尽头就是null
constructor属性:prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数
function P(){}
P.prototype.constructor === P // Function: P
由于constructor属性定义在prototype对象上面,意味着可以被所有实例对象继承
function P() {}
var p = new P();
p.constructor === P // true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false
constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的
修改原型对象时,一般要同时修改constructor属性的指向
如果不能确定constructor属性是什么函数,还有一个办法:通过name属性,从实例得到构造函数的名称
function Foo() {}
var f = new Foo();
f.constructor.name // "Foo"
__proto__属性:这个属性是每个实例对象特有的(当然构造函数也有),指向函数的prototype原型对象,让实例找到自己的原型对象,查找原型链用的
function P(){}
var p = new P();
p.__proto__ === P.prototype // P {}
function A(){}
var a = new A();
A.prototype // A {}
Function.prototype // [Function]
Object.prototype // {}
a.__proto__ // A {}
a.__proyo__.__proto__ // {}
a.__proto__.__proto__.__proto__ // null
Object对象相关方法:
-
Object.getPrototypeOf():返回参数对象的原型。这是获取原型对象的标准方法var F = function () {}; var f = new F(); Object.getPrototypeOf(f) === F.prototype // true下面是几种特殊对象的原型
// 空对象的原型是 Object.prototype Object.getPrototypeOf({}) === Object.prototype // true // Object.prototype 的原型是 null Object.getPrototypeOf(Object.prototype) === null // true // 函数的原型是 Function.prototype function f() {} Object.getPrototypeOf(f) === Function.prototype // true -
Object.setPrototypeOf():为参数对象设置原型,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象var a = {}; var b = {x: 1}; Object.setPrototypeOf(a, b); Object.getPrototypeOf(a) === b // true a.x // 1 -
Object.create():该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性// 原型对象 var A = { print: function () { console.log('hello'); } }; // 实例对象 var B = Object.create(A); Object.getPrototypeOf(B) === A // true B.print() // hello B.print === A.print // true -
Object.prototype.isPrototypeOf():实例对象的isPrototypeOf方法,用来判断该对象是否为参数对象的原型var o1 = {}; var o2 = Object.create(o1); var o3 = Object.create(o2); o2.isPrototypeOf(o3) // true o1.isPrototypeOf(o3) // true -
Object.prototype.__proto__:实例对象的__proto__属性(前后各两个下划线),返回该对象的原型 -
获取原型对象方法的比较:
obj.__proto__obj.constructor.prototypeObject.getPrototypeOf(obj)推荐
-
Object.getOwnPropertyNames:返回一个数组,成员是参数对象本身的所有属性的键名,不包含继承的属性键名 -
Object.prototype.hasOwnProperty():返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上 -
Object.keys():用来遍历对象的属性,参数是一个对象,返回一个数组;该数组的成员都是该对象自身而不是继承的所有属性名 -
Object.getOwnPropertyNames():返回包括不可枚举的所有属性名 -
Object.prototype.valueOf():返回一个对象的‘值’,默认情况下返回对象本身 -
Object.prototype.toString():返回一个对象的字符串形式,默认情况下返回类型字符串,其主要应用是判断数据类型
参考链接
- 网道/WangDoc.com-JavaScript教程/面向对象编程-wangdoc.com/javascript/….
- Object对象的静态方法-wangdoc.com/javascript/…