面向对象编程
这是一篇简单介绍js面向对象的编程笔记
一,js解析和执行
js解析时,会先把var和function声明的变量和函数放到一个词法对象里,变量的值是undefined,函数则是一个引用,这是js变量提升的根本机制。
如果一个变量没有声明就开始使用了,不论它是在局部还是全局使用,它都是挂在了window下,是个全局变量,这点和this指向很类似,this找不到直接调用者,也是直接指向window。
如果变量名和函数名命名冲突了,则该命名最后指向的是函数。如果两个函数名重复了,则后面一个函数覆盖前面的,因为在js中,函数才是第一等公民。
e.g. console.log(a)//undefined var a = 1 console.log(a)//1 e.g. console.log(aa)//function aa() {console.log('bbbbb')} var aa = 1 function aa() { console.log('aaaaa') } function aa() { console.log('bbbbb') }
二,作用域
js只有全局作用域和局部(函数)作用域,并且内部可以由内到外,从近到远,访问即止地层层访问外部变量,形成一条作用域链,而外部不能直接访问内部变量。
e.g. var a = 1 var b = function() { var c = 2+a console.log(c) } b()//3 console.log(c)//undefined
三,闭包
一种访问局部变量的手段,通常指的是一个可以访问局部变量的函数。
闭包一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,因此要注意控制性能,提高网页性能。
e.g. var name = "The Window"; var object = { name : "My Object", // 这个方法是返回了一个函数 getNameFunc : function(){ return function(){ return this.name; }; } }; // 执行了object.getNameFunc()返回的函数,此时this找不到直接调用者,直接指向window alert(object.getNameFunc()()); e.g. var name = "The Window"; var object = { name : "My Object", // 这个方法是返回了一个函数 getNameFunc : function(){ // that指向的是object var that = this; return function(){ return that.name; }; } }; // 执行了object.getNameFunc()返回的函数,此时this固定化成了object,弹出object.name alert(object.getNameFunc()());
四,类与对象
搞清楚js中的类,就需要明白三个概念:构造函数、原型对象,实例化和实例。
e.g. function Person(name,age) { this.name = name; this.age = age } Person.prototype.hello = function () { console.log('Hello,I am '+this.name+',and I am '+this.age) } var xm = new Person('xiaoming',12) xm.hello()//Hello,I am xiaoming,and I am 12 Person.prototype = { asset:12345 } var xmm = new Person('xiaomingming',34) console.log(xm.asset)//undefined console.log(xmm.asset)//12345
其中Person函数就是构造函数,Person.prototype指向的对象就是原型对象,xm就是实例
那么在关键词new的过程中,大概发生了什么呢?
1,一个新对象被创建了,继承Person.prototype原型对象里的属性。
2,使用指定的参数调用构造函数Person,并将this指向新创建的对象。
如果我们用自定义函数模拟过程,则:
e.g. function Person(name,age) { this.name = name; this.age = age } function New(f) { return function() { var o = { '__proto__':f.prototype } f.apply(o,arguments) return o } } var xmmm = New(Person)('xmmm',232)
在js中,并没有真正的类的概念,只是用构造函数和原型对象模拟了类,其中原型对象是类的唯一标识,当且仅当两个对象继承自同一个原型对象的时候,我们才说它们是同一个类的实例。而初始化对象的构造函数则不能作为类的标识,通常构造函数的名字被用来当做类名了而已。
instanceof 运算符
实例 instanceof 类名 被用来判断一个实例是否继承自某个原型对象,返回布尔值。
在上述例子中,原型对象也是对象,也具有原型对象,即原型对象的原型对象,以此类推。访问一个实例本身没有的属性或方法时,js会从原型对象上去找,原型对象上没有时,会从原型对象上的原型对象上去找,......这种像链式一样地继承机制就是原型链,和作用域链很类似。
原型对象中constructor属性则是指向该原型对象的构造函数,在修改原型对象时,比如增删其属性时,可采用.符号操作,如Person.prototype.hello = s =>{console.log(s)},在实例person初始化的时候,该原型对象的constructor默认指向构造函数.
但是如果直接给Person.prototype赋值对象型数据时,则需要显式指定constructor属性,否则原有的原型对象会被新对象所替代,而通过构造函数实例化的实例的__protoo__属性都会指向最新的Person.prototype对象。在这个改动之后实例化的实例均不能继承原有的原型对象的属性,改动之前的实例亦不能继承新原型对象的属性。
实例,构造函数,原型对象,原型链之间的关系可以借助一张神图来说明关系
this指针问题:
1,总是指向方法的直接调用对象,在事件触发中指向触发该事件的dom节点对象
2,指向new出来的实例
apply和call均可以改变this指针,其区别仅仅是第二个参数的不同而已:
f.call(o,a,b) === o.f(a,b) === f.apply(o,[a,b])
五,封装
一段代码能够实现某种功能,隐藏该代码的细节,仅对外开放接口去使用该功能代码,即是对该功能代码的封装。
六,继承
继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。具有的方式可能是复制,可能是引用。
从对象层面上来看继承:
1,复制
var zsh = { name :'zsh', age:23 } var job = { job:'programer', language:'fe' } for (var key in job){ zsh[key] = job[key] } console.log(zsh)//{name: "zsh", age: 23, job: "programer", language: "fe"}
涉及到深浅复制参考:http://www.cnblogs.com/zhouxiaohouer/p/8037729.html
2,Object.create(proto[, propertiesObject])
es5里的方法,注意兼容性。
参数
proto 新创建对象的原型对象。
propertiesObject 可选。如果没有指定为 undefined,则是要添加到新创建对象的可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数。
返回值
在指定原型对象上添加新属性后的对象。
从类的层面上来看继承:
// 创建父类 // 创建子类 // 建立联系,修正构造函数 // 实例化 function Father() {} function Son() {} Son.prototype = Object.create(Father.prototype) // 此时,Son.prototype.constructor指向的是function Father() {},我们最好是将其修正过来 Son.prototype.constructor = Son
七,多态
'见人说人话,见鬼说鬼话。'
根据不同的参数个数,调用不同的方法,实现不同的功能
本质上是检测arguments中数据类型来做对应的操作,将差异化的处理方式通用封装处理。
e.g. function duotai(a,b,c,d,e,f) { console.log(duotai.length)//返回形参个数 console.log(arguments.length)//返回实参个数 } duotai(1,2,3,4)//6 4 e.g. function add() { var result = 0 for (var i = 0 ;i<arguments.length-1;i++){ result += arguments[i] } return result } add(1,2,3,4,6,7,8)//31 add(343,567,8456,234)//9600