prototype与自定义类
任何一个函数都有prototype属性。
访问自带的构造函数Array的原型
Array.prototype //访问结果为 [ ]
访问自定义构造函数Robot的原型
function Robot(name) {
this.name = name;
this.say = function(){
console.log(this.name)
}
};
Robot.prototype = { age: 12, gender: "boy" } //设置构造函数Robot的
原型对象(属性值可以为函数)
Robot.prototype //Object { age: 12, gender: "boy" }
但是一般只有当把一个函数当构造函数使用时,我们才使用prototype。 上一节讲到,我们自定义类的时候,使用的那种方式是费内存的,我们使用prototype来定义就会不同。
function Car(x,y){
this.x = x;
this.y = y;
}
Car.prototype.run = function(x_increase, y_increase){
this.x += x_increase;
this.y += y_increase;
}
Car.prototype.show = function(){
console.log("( " + this.x + "," + this.y + " )");
}
这样当我们new Car的时候,run和show并不会每次都分配内存,所以会比较省内存。
当我们得到一个实例之后,访问实例的原型,使用proto属性.(注:proto不是标准属性,但是被大多数浏览器支持.)
var Robot = function(name) {
this.name = name;
this.say = function(){
console.log(this.name)
}
};
Robot.prototype = { age: 12, gender: "boy" } //设置构造函数Robot的原型对象(属性值可以为函数)
var robot = new Robot("bower") //实例化一个对象
robot.__proto__ //Object { age: 12, gender: "boy" }
new关键字
当我们,执行
new Robot("bower")
实际上是得到了一个从Robot.prototype继承而来的一个对象。如果要理解原型继承中new的意义,还是这样理解最好。
如果我们要描述new的工作流程,如下:
1.分配一个新的空对象.
2.设置新对象的相关属性、方法,例如继承Robot.prototype上的各式方法、属性。注意,这里执行的并不是拷贝,而是类似于引用的方法,我们叫它代理(比如Robot.prototype对象的属性发生变化时,由Robot生成的实例对象直接继承的相应属性也会变化)。
3.将这个新对象作为构造函数的执行上下文(其this引用指向这个新的对象),并执行构造函数.
4.返回这个对象到执行new Robot("bower")的位置,赋值给前面的变量名.
var Robot = function(name) { //声明一个构造函数
this.name = name;
this.say = function(){
console.log(this.name)
}
};
Robot.prototype = { age: 12, gender: "boy" } //设置构造函数Robot的原型对象(属性值可以为函数)
var robot = new Robot("bower") //实例化一个对象
当执行new Robot("bower")时
首先,其中Robot.prototype这个对象的值{age:12,gender:"boy"}会被新的空对象继承,所以后面robot.age的值是12,robot.gender的值是"boy".
然后,执行构造函数,运行时执行的方法可以理解为Robot.apply(robot,["bower"])的方式.此时会去设置新对象的属性name="bower";say=function(){this.name}.
所以后面robot.name的值是"bower".执行robot.say()会输出"bower".
其实,最后才把新的对象的值赋给变量robot.
原型继承
Javascript对象(如robot)拥有自有属性(如通过构造函数this.name=name设置的属性)和继承属性(例如代理自Robot.prototype的属性)两种。
在查询对象robot的属性age时,先查找robot中自有属性的age属性,如果没找到,则查找robot继承属性(也就是robot的原型对象:robot.proto)中的age属性,直到查找到age或者一个原型是null的对象为止.
在给对象robot的age属性赋值时,如果robot自有属性中已经有一个属性age,则改变age的值,若robot中不存在自有属性age,只存在继承属性age或者所有属性中都没有age属性,则为robot创建一个自有属性age并为其赋值.
也就是说,只有在查询时原型链才会起作用。赋值只针对自有属性.
var Robot = function(name) { //声明一个构造函数
this.name = name;
this.say = function(){
console.log(this.name)
}
};
var info = { age: 12, gender: "boy" };
Robot.prototype = info;
var robot = new Robot("bower");
console.log(robot); //打印结果为Robot {name: "bower", say: function, age: 12, gender: "boy"}
console.log('age' in robot); //打印结果为true
console.log(robot.hasOwnProperty('age')); //打印结果为false
robot.name = "cup";
robot.age = 13;
console.log(robot); //打印结果为Robot {name: "cup", say: function, age: 13, gender: "boy"}
console.log('age' in robot); //打印结果为true
console.log(robot.hasOwnProperty('age')); //打印结果为true
可以使用in 或者 hasOwnProperty 来判断对象中是否存在属性或者是否存在自有属性。
原型链
Javascript基于 ‘proto’ 的原型链
我们知道实例对象存在自有属性和继承属性两种,利用原型继承的方法我们有能力创建一个看似矛盾的对象,该对象本身即可以被执行,又可以存取数据.
即创建一个可执行对象.既可以当作对象来使用(有原型链),也可以当作函数来直接调用.
例如
可以执行
robot();
也可以通过
robot.name = "bower"
来给robot对象设置属性值.
function SuperRobot(data) {
var say = function() { return "Hello World!"; }
say.__proto__ = data;
return say;
}
var data = { name:"atom", age: 5};
var super_robot = SuperRobot(data);
console.log(super_robot()); //Hello World!
console.log(super_robot.age); //5
console.log(typeof super_robot); //function
回调函数
一个回调函数(例如“function_1”),是一个被作为参数传递给另一个函数(例如“function_2”)的函数,回调函数在function_2中被调用。一个回调函数本质上是一种编程模式(为一个常见问题创建的解决方案),因此,使用回调函数也叫做回调模式。
下面是另一个Javascript中典型的回调函数的例子:
var function_1=function(callback){
console.log("do something.");
(callback && typeof(callback) === "function") && callback();
}
var function_2=function(){console.log("function_2 is running.")};
function_1(function_2);
//callback && typeof(callback) === "function" 的作用是判断参数callback是否被传入,并且判断该参数是否是一
上面的代码是将一个具名函数function_2传入函数function_1.其实际效果等同于下面的匿名函数传递.
var function_1=function(callback){
console.log("do something.");
(callback && typeof(callback) === "function") && callback();
}
function_1(function(){console.log("function_2 is running.")});
在此,注意到我们将一个匿名函数(没有名字的函数)作为参数传递给了function_1,当function_1中调用执行该匿名函数callback时才会去执行该传入的函数,也就是说,该匿名函数只是在合适的时机去被执行。
使用场合
1.资源加载:动态加载js文件后执行回调,加载iframe后执行回调,ajax操作回调,图片加载完成执行回调,AJAX等等。
2.DOM事件及Node.js事件基于回调机制(Node.js回调可能会出现多层回调嵌套的问题)。 setTimeout的延迟时间为0,这个hack经常被用到,settimeout调用的函数其实就是一个callback的体现
3.链式调用:链式调用的时候,在赋值器(setter)方法中(或者本身没有返回值的方法中)很容易实现链式调用,而取值器(getter)相对来说不好实现链式调用,因为你需要取值器返回你需要的数据而不是this指针,如果要实现链式方法,可以用回调函数来实现
4.setTimeout、setInterval的函数调用得到其返回值。由于两个函数都是异步的,即:他们的调用时序和程序的主流程是相对独立的,所以没有办法在主体里面等待它们的返回值,它们被打开的时候程序也不会停下来等待,否则也就失去了setTimeout及setInterval的意义了,所以用return已经没有意义,只能使用callback。callback的意义在于将timer执行的结果通知给代理函数进行及时处理。
可能你对上面的使用场景并不熟悉,但不必担心,在我们的高级课程和html的相关课程中会有相关知识点的介绍,只要在今后使用时能够意识到"我这是使用了回调模式"就可以了.
到目前为止,我们将函数作为参数传递给了另一个函数或方法。在我们学习更多的实际例子和编写我们自己的回调函数之前,接下来先来理解回调函数是怎样运作的。
回调函数的运行
函数在Javascript中是第一类对象,我们可以像对待对象一样对待函数,因此我们能像传递变量一样传递函数,在函数中返回函数,在其他函数中使用函数。当我们将一个回调函数作为参数传递给另一个函数时,我们仅仅传递了函数的定义,我们并没有在参数中执行该函数。我们并不传递像我们平时执行函数一样带有一对执行小括号()的函数,而只是传递函数名。
var function_1=function(callback){
var word_1 = "do something.";
var word_2 = "do another thing.";
console.log(word_1);
(callback && typeof(callback) === "function") && callback(word_2);
}
var function_2=function(word){console.log(word)};
function_1(function_2);
需要注意的很重要的一点是回调函数并不会马上被执行。它会在包含它的函数内的某个特定时间点被“回调”(就像它的名字一样)。
//全局变量
var info = [];
//普通的show函数,将数据的内容打印到控制台
function show(data){
info.push(data)
//如果是可以直接输出的字符串则直接输出
if ( typeof data === "string")
{
console.log(data);
}
else if ( typeof data === "object"){
//遍历data
for(var item in data){
console.log(item + ": " + data[item]);
}
}
}
//定义一个接收两个参数的函数,参数中后面一个是回调函数, 在函数体中调用回调函数,并将第一个参数传入回调函数.
function get_inputs(object,function_1){
(function_1 && typeof(function_1)==="function")&&function_1(object);
}
//当我们调用get_inputs函数时,我们将show函数作为一个参数传递给它
//因此show将会在get_inputs函数内被回调(或者执行)
get_inputs({name:"bower",speciality:"Robot"}, show);
//name: bower
//speciality: Robot