一、function的直接定义和存在变量里区别
平时我们定义函数有两种方式:
- 直接定义
- 定义一个变量,将函数赋值给这个变量
这两中定义方式有什么区别呢?
- 直接定义的函数,可以直接调用,因为存在变量提升,不需要提前声明。
- 函数变量在调用前需要提前声明。
测试实例:
f_func(); //直接定义的函数无需提前声明
v_func(); //函数变量,未声明时调用失败
function f_func() {
console.log("我是直接定义的函数f_func");
}
var v_func = function() {
console.log("我是函数变量v_func");
}
v_func(); //声明后才可以调用
将第一个 v_func();
注释掉之后,再运行一次。这次的运行结果正常了。
上面的运行结果可以看出,将函数存在变量里面,在调用前需要提前声明。
另外还有一点就是,在页面直接定义一个函数,相当于直接创建了一个全局变量。
上面的定义方式从功能层次来说没有问题,但是在团队开发中不建议这样做。
首先是直接定义的方式,如果别人也定义了相同的方法,原功能就会被覆盖掉。
其次是函数变量的方法,减少了覆盖和被覆盖的可能性,当然一旦被覆盖,所有的功能将会失效,这种现象是很明显的,我们可以轻易察觉到。
解决方法
用对象收编变量
可以创建一个对象,将我们自己写的方法放进去。
var Test = {//属性:值
func1:function(){
console.log("我是func1");
},
func2:function(){
console.log("我是func2");
},
func3:function(){
console.log("我是func3");
},
}
Test.func1();//使用点运算符调用对象里的方法
对象的属性是可以动态添加的,所以上面的定义方式有另一种形式。
var Test = function() {};
//动态给对象添加属性
Test.func1 = function() {
console.log("我是func1");
}
Test.func2 = function() {
console.log("我是func2");
}
Test.func3 = function() {
console.log("我是func3");
}
Test.func3(); //使用点运算符调用对象里的方法
上面这种方式,只创建了一个全局对象,而且有效的解决了方法覆盖和被覆盖的问题,但是随之而来的另一个问题就是,如果别人想用你对象里的方法,该怎么办呢?
首先这是一个对象,这个对象是不能复制一份的,或者说这个对象在用new关键字创建新的对象的时候,新创建的对象是不会继承这些方法的。
var Test = function() {};
Test.func1 = function() {
console.log("我是func1");
}
Test.func2 = function() {
console.log("我是func2");
}
console.log("对象:",Test);
var OtherTest = new Test();
console.log("通过new创建新的对象:",OtherTest);
var AnotherTest = Test;
console.log("直接复制对象:",AnotherTest);
上图可以看出,我们添加进去的方法确实都没有继承到。那么,该怎么解决呢?
真假对象
函数对象
如果是简单的复制,可以将方法放在一个函数对象中,然后return出去。
var Test = function() {
return {
func1:function(){
console.log("func1");
},
func2:function(){
console.log("func2");
}
}
}
var A = Test();
A.func1();
var B = Test();
B.func2();
上面这种写法,每次别人调用这个函数的时候都返回了一个新的对象,明面上执行的是Test对象,实际上返回的是新对象,这样每个人使用的时候就互不影响了。
类
上面的方法,并不是一个真正意义上的类的创建,并且创建出来的A对象B对象和原始的Test对象一点关系都没有。所以我们可以改造一下,将对象改造成类。
var Test = function() {
this.func1 = function (){
console.log("func1");
}
this.func2 = function (){
console.log("func2");
}
}
console.log("瞅瞅类的样子:",Test);
var A = new Test();//类要使用new关键字
A.func1();
console.log("瞅瞅类的实例:",A);
我们把所有的方法放在了函数的内部,通过this定义的,所以每一次通过new关键字创建新对象的时候,新创建的对象都会对类的this上的属性进行复制。所以这些新创建的对象都会有一套属于自己的方法。
但是上面的这种操作,有时候造成的消耗是很大的,所以我们需要再进行优化一下。
创建一个Test类
var Test = function (){
Test.prototype.func1 = function (){//将方法添加到原型上
console.log("func1");
}
Test.prototype.func2 = function(){
console.log("func2");
}
}
var A = new Test();
A.func1();
console.log(A);
这里涉及到了原型,没有相关知识的可以先去了解一下再看,从上面的图可以看出,优化后new出来的实例,方法都在prototype上,也就是原型上。
这样做和直接写在this上有什么区别呢?想一下我们为什么要进行优化,内存,也就是说,这样优化后,造成的内存消耗没那么多。
那么为什么呢?用生活中的例子可能会更好理解:
this
我们把方法写在this上面,那么每次new一个实例的时候,就会复制一遍,相当于,我画了一本画册(类),里面有3幅画(方法),画上面记录了我去过的旅游景点(方法实现的功能),然后小明想要看看我第2幅画(调用方法),我就给他画了一本画册(创建实例),这个过程,我把那3幅画重新画了一次。小红来了想看第1幅画,我又画了一本画册,又把那3幅画重新画了一次。
画给这两个人两本画册,消耗纸张 2×3=6
prototype
把方法定义在原型上呢,就相当于,小明想看看我第1幅画,我直接把画册的第1幅画给他看,小红想看第2幅画,我就直接把画册的第2幅画给她看。
消耗纸张 0
上面两个过程,小明和小红都看到了自己想看的那幅画,但是对于“我”而言,消耗却是不一样的,很明显把方法写在原型上对我来说消耗更小。
原型的另一种写法
两种写法不能混着用,只能选择一种。
var Test = function() {}
Test.prototype = {
func1: function() {
console.log("func1");
},
func2: function() {
console.log("func2");
}
}
方法的另类用法(链式)
正常情况:
var Test = function() {}
Test.prototype = {
func1: function() {
console.log("func1");
},1
func2: function() {
console.log("func2");
}
}
var A = new Test();
A.func1();
A.func2();
看最后两行代码,我们写了两次对象A,这个操作时可以避免的,只需要在声明的每一个方法末尾处将当前对象(this)返回,在JS中this就是指向当前对象的。
另类用法:
var Test = function() {}
Test.prototype = {
func1: function() {
console.log("func1");
return this;
},
func2: function() {
console.log("func2");
return this;
}
}
var A = new Test();
A.func1().func2();
像上面最后一行代码的这种调用方式,称为 链式调用。
函数的祖先
prototype.js
一款JS框架,最大的特点是对原生对象的拓展。
比如想个每个函数都添加一个 func3
方法,可以这样做:
Function.prototype.func3 = function (){
console.log("func3");
}
//函数形式
var f = function () {}
f.func3();
//类形式
var m = new Function();
m.func3();
当然,上面是举个例子,一般不建议这样做,因为这样做污染了原生对象Function,进而别人创建的函数也会被污染,造成不必要的开销。
不过可以通过抽象出一个统一添加方法的功能方法。
//抽象方法
Function.prototype.addMethod = function(name,fn){
this[name] = fn;
return this;//这样就可以链式添加
}
var methods = Function();
methods.addMethod('func3',function(){
console.log('func3');
}).addMethod('func4',function(){
console.log('func4');
});
//函数式调用
methods.func3();
methods.func4();
对于习惯使用类式调用的开发者,需要把代码更改一下:
Function.prototype.addMethod = function(name,fn){
this.prototype[name] = fn;
return this;//这样就可以链式添加
}
var methods = function(){};
methods.addMethod('func3',function(){
console.log('func3');
}).addMethod('func4',function(){
console.log('func4');
});
注意,这个写法不能直接调用函数了
methods.func3();
应该通过new关键字来创建一个新的实例对象,然后再调用方法。
var B = new method();
B.func3();
本章完。