之前我们自定义对象时,以构造函数为模板;
对象的属性和方法,可以定义在构造函数内部。
以上代码其中的颜色重复,如果我们将颜色拿出来,放在公用的地方来实现属性共享,节省内存;
JavaScript 的每个对象都继承另一个对象,父级对象称为“原型”(prototype)对象。
只有null除外,它没有自己的原型对象。
而原型对象上的所有属性和方法,都能被派生对象共享
通过构造函数生成实例对象时,会自动为实例对象分配原型对象。
每一个构造函数都有一个prototype属性,这个属性就是实例对象的原型对象。
原型对象的属性不是实例对象自身的属性。
但是只要修改原型对象,变动就立刻会体现在所有实例对象上
如果实例对象自身就有某个属性或方法,那么原型对象上的属性和方法便会失效;
总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。
这也是它被称为原型对象的原因,而实例对象可以视作从原型对象衍生出来的子对象。
由于 JavaScript 的所有对象都有构造函数(只有null除外),
而所有构造函数都有prototype属性(其实是所有函数都有prototype属性),所以所有对象都有自己的原型对象。
对象的属性和方法,有可能是定义在自身内,也有可能是定义在它的原型对象上。
由于原型本身也是对象,又有自己的原型,所以形成了一条原型链(prototype chain)。
比如,a对象是b对象的原型,b对象是c对象的原型,以此类推。
如果一层层地上溯,所有对象的原型最终都可以上溯到Object;
那么,Object对象有没有它的原型呢?
有的,就是null,而null对象没有自己的原型。
对象.__proto__ (两边都是两个下划线):获取对象的原型对象
原型链的作用是,读取对象的某个属性时,JavaScript 引擎先寻找对象本身的属性,
如果找不到,就到它的原型去找,如果还是找不到,就到原型的原型去找。
如果直到最顶层的Object.prototype还是找不到,则返回undefined。
如果对象自身和它的原型,都定义了一个同名属性,那么优先读取对象自身的属性,这叫做“覆盖”(overriding)。
需要注意的是,一级级向上,在原型链寻找某个属性,对性能是有影响的。
如果寻找某个不存在的属性,将会遍历整个原型链。
注意:
在实际项目开发中,我们通常会使用第三方框架(一个类),但是当我们发现这个类
中并不存在我们想要属性或方法时,不能直接修改源代码,
但是可以通过原型对象的添加我们自己的属性或方法。
1.4.1 constructor 属性
对象有一个constructor属性,指向原型对象所在的构造函数;
1.4.2 设置获取原型对象
Object.getPrototypeOf() 方法返回一个对象的原型对象。
这是获取原型对象的标准方法。
Object.setPrototypeOf() 为现有对象设置原型对象
第一个是现有对象,第二个是要设置成为原型对象的对象
这是设置原型对象的标准方法。
__proto__ 属性:
前面用proto来获取原型对象,如果给proto属性赋值,则是设置原型对象;
但是,不建议使用,而是用Object.getPrototypeof()(读取)和Object.setPrototypeOf()(设置),进行原型对象的读写操作
1.4.3 获取原型对象方法及比较
通过上面的学习,我们知道,获取实例对象obj的原型对象,有三种方法。
> obj.__proto__
> obj.constructor.prototype
> Object.getPrototypeOf(obj)
上面三种方法之中,前两种都不是很可靠。
最新的ES6标准规定,proto属性只有浏览器才需要部署,其他环境可以不部署。
而obj.constructor.prototype在手动改变原型对象时,会失效。
推荐使用第三种Object.getPrototypeOf方法,获取原型对象。
JavaScript有两种作用域:全局作用域和函数作用域。函数内部可以直接读取全局变量。
函数 f1 可以读取全局变量 n。
但是,在函数外部无法读取函数内部声明的变量。
但是,有时我们却需要在函数外部访问函数内部的变量;
正常情况下,这是办不到的,只有通过变通方法才能实现。
那就是在函数的内部,再定义一个函数。
function f1() {
var n = 999;
var f2 = function() {
console.log(n);
}
return f2;
}
var f = f1();
f();
上面代码中,函数f2就在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。
但是反过来就不行,f2内部的局部变量,对f1就是不可见的。
这就是JavaScript语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。
所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!
闭包就是函数f2,即能够读取其他函数内部变量的函数。
由于在JavaScript语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。
闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从f2可以得到f1的内部变量。
在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁;
function f1() {
var n = 99;
console.log(++n);
}
f1(); //100
f1(); //100
当我们在函数内部引入一个变量或函数时,系统都会开辟一块内存空间;
还会将这块内存的引用计数器进行初始化,初始化值为0
如果外部有全局变量或程序引用了这块空间,则引用计数器会自动进行+1操作
当函数执行完毕后,变量计数器重新归零,系统会运行垃圾回收机制,将函数运行产生的数据销毁;
如计数器不是 0 ,则不会清楚数据;
这个过程就称之为 "JS的垃圾回收机制" ;
闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,
即闭包可以使得它诞生环境一直存在;
注意,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。
因此不能滥用闭包,否则会造成网页的性能问题。
回顾this的指向:
全局作用域下,this执向window对象;
构造函数中,this指向实例化对象;
我们能不能在调用函数时直接修改函数内部的this指向呢?
答:使用call或apply方法
函数名称.apply(obj,[arg1,arg2...,argN])
参数说明:
obj :this要指向的对象
[arg1,arg2...argN] : 参数列表,但是要求格式为数组
两者的功能完全一致,都是为了改变函数内部的this指向,唯一的区别在于参数传递方式不同
call方法可能有多个参数,第一个为要指向的对象,其他参数为函数的实参;
apply方法最多只能有两个参数,第一个为要指向的对象,第二个为数组,数组内容为函数的实参;
在PHP类中我们使用private声明私有属性;
public :公有的
protected :受保护的
private :私有的
但是在JS中,只有两种属性,公有属性与私有属性,在构造函数内部通过this声明的属性就是公有属性,通过var声明的就是私有属性。
在PHP中,我们可以使用 extends 关键字来实现类的继承;
但是,JS中并没有类似 extends 的关键字提供继承的功能;
而我们知道,所谓的继承,其实就是为子类提供父类中的方法和属相,使子类能够使用父类中的属性及方法;
1:通过原型实现继承;
2:通过call或apply方法继承;
JavaScript提供定时执行代码的功能,叫做定时器:
setTimeout()
用来指定某个函数或某段代码,在多少毫秒之后执行;
setInterval()
指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行。
setTimeout()、setInterval()的第一个参数都是指定要执行的函数名称或者代码段,第二个参数时时间,以毫秒为单位;
clearTimeout(),clearInterval()
setTimeout和setInterval函数,都返回一个表示计数器编号的整数值,
将该整数传入clearTimeout和clearInterval函数,就可以取消对应的定时器。
我是小咖
发个邀请:
如果你正好想学习php,可以与我一起交流,我的VX:feilueze333。下面资料免费赠送