函数是对象

  ECMAscript中的函数实际是function类型的对象实例,因此函数也像对象一样具有属性和方法。而js中的继承,自定义引用类型的实现,均依靠function对象中的prototype属性。

函数名是函数对象的指针

  类比普通对象的创建过程。

var obj = new Object();

  变量obj中保存的实际上是新创建的对象在堆内存中的地址,也就是一个指针。而通过函数声明或者构造函数获得的函数对象,其函数名也是这个函数对象的一个指针,因此,如果定义两个相同函数名的函数,后定义的函数名将覆盖前者,这个过程就如变量被重新赋值一样(因此javascript中没有重载)。

function sum (a,b) {
    return a + b;
}
function sum (a,b) {
    return a - b;
}
sum(100,50);		//50;

函数的声明提升

  在一般的使用中,函数声明和函数表达式将获得同样的结果。而如果一个函数调用发生在函数声明之后,也不会产生错误。

  而实际上,解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用;至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

  在C语言中,调用未声明的函数是不可行的。

max(2,3)			//错误,函数未定义
int max(int a,int b){
    return a>b?a:b;
}
max(2,3)			//3

  而javascript声明提升的特性使得我们可以在任意地方调用函数,只要它在某一处被声明过。

作为值的函数

  函数名本身是变量,因此

  • 可以把一个函数当作参数传递给另一个函数。
  • 可以在一个函数中返回另一个函数。

  在函数作为参数传递的过程中,首先要明确的一点是要访问函数的指针而不是执行它的话,应该去掉函数名后的括号

function sum (a,b) {
   return a+b;
}
function add (otherFunction , item1 , item2){ 	//函数作为参数传递
    return otherFunction(item1,item2);			//实际上这个函数纯属多余。只是想说明一下函数能作为参数传递。。
}
add(sum,3,4);		// 7

  在一个函数中返回另一个函数,能想到最接地气的例子是数组的sort排序方法。在sort方法中,需要我们指定一个排序的依据(比较函数)。

var arr = [1,4,5,0,3,2,6];
arr.sort(function(a,b){return a - b}); // [0, 1, 2, 3, 4, 5, 6]

  考虑如果需要以某类对象的某属性值为依据排序的情况。

var arr = [{name :'o',score :18},{name :'c',score :12},{name :'x',score :11},{name :'l',score :19},{name :'a',score :22},];
function compare(a,b) {
    return a.score - b.score;
}
arr.sort(compare);      // compare函数作为参数,回调而非调用
//    0:{name: "x", score: 11}
//    1:{name: "c", score: 12}
//    2:{name: "o", score: 18}
//    3:{name: "l", score: 19}
//    4:{name: "a", score: 22}

  如果事先指定了比较的属性,那么上面的函数完全可以实现需求。而如果在追求复用性的情况下,要求传递一个属性即可按此属性值给对象数组排序。

var arr = [{name :'o',score :18},{name :'c',score :12},{name :'x',score :11},{name :'l',score :19},{name :'a',score :22},];
function compare(property) {
    return function(obj1,obj2){
        return obj1[property] - obj2[property];
    }
}
arr.sort(compare('score'));   //调用而非回调。compare函数返回的匿名函数作为sort方法的回调函数。
//    0:{name: "x", score: 11}
//    1:{name: "c", score: 12}
//    2:{name: "o", score: 18}
//    3:{name: "l", score: 19}
//    4:{name: "a", score: 22}

  这个例子中,比较函数需要取到数组中保存的对象的一个属性并返回比较结果,外层的compare函数接收比较的属性名,return一个以两个对象为参数的匿名函数,最终在匿名函数中取得需要比较的对象的属性,并返回比较结果。

  在这插嘴记叙一下js操作对象属性时圆点操作符和用中括号操作符的不同之处。

  • 中括号运算符可以用字符串变量的内容作为属性名。

  • 中括号运算符可以用纯数字为属性名。

  • 中括号运算符可以用js的关键字和保留字作为属性名。

  也就是说。*中括号运算符总是能代替点运算符。但点运算符却不一定能全部代替中括号运算符。 *因为例子中属性名是以字符串的形式传递,因此用圆点则会出现函数中取不到对象属性的情况。

我饿了。明天更新。

===============我是一条分割线================

emmm今天520,虐狗的节日那么多。。。嗝~~

==================废话少说==================

函数内部的对象

  函数本身是一个对象,其内部有两个特殊的对象 – arguments以及this。

arguments

  arguments中保存着传入函数的所有参数,而js函数不限制传入参数的多少、不介意传入参数类型特性的实现,应该也归功于arguments对象。由于可以通过类此访问数组元素的方法访问arguments对象的成员,因此也称其为伪数组对象。arguments对象 也有length属性,其值也类似数组的length属性,表明了函数中参数的个数。

function print (a,b) {
    for(var i = 0; i < arguments.length; i++){
        console.log(arguments[i]);
    }
}
print(1,2,'3',[4]);
// 1
// 2
// 3 (字符串)
// [4] (数组)

  这个例子应该很能说明问题了。。形参只有两个(a和b),但是调用的时候传进去4个类型不一的参数,所以arguments.length = 4,循环打印了4个不同类型的输出。

this

  this使用时非常灵活。个人觉的是js里第二有趣的部分,第一有趣的部分?大概是原型吧。。

  this引用的是函数执行时的环境对象,所以在函数未发生调用时,this的指向是不可确定的。this的指向和程序的执行过程密不可分,也不能脱离具体的执行环境分析this的指向。

function sayId () {
    console.log(this.id);
}
id = 1;
var obj = {
    id: 2,
    objson: {
   		id: 3,
	}
}
obj.sayId = sayId;	// 为obj对象添加sayId方法。
obj.objson.sayId = sayId;	// 为obj对象中的objson对象添加sayId方法。
window.sayId();		// 1;
obj.sayId();		// 2;
obj.objson.sayId();	 // 3;

  sayId引用了this,当window对象调用sayId时(window可省略不写),this指向window,而当obj对象调用sayId时,this指向obj。因此this总指向调用它的对象

  进一步的,当obj.objson.sayId()调用sayId时,this并未指向obj,而是指向了obj对象中的objson对象,因此可得出结论,this总指向最后调用它的对象。所以在分析this指向问题时,不妨从调用处开始分析,在不同的函数调用处分析当前this的指向,抽丝剥茧。

函数的属性和方法

  函数是对象。函数是对象。函数是对象。。。。。

  对象会有属性和方法,所以函数也有属性 – prototype、length,方法 – call、apply。

属性

length

  一句话说明:函数的length属性 = 形参的个数,所以函数的length说明了函数期望得到的参数个数。

prototype

  prototype,原型。js是面向对象的语言,可它没有函数签名所以重载靠检测参数类型模拟;没有类的概念(es2015好像是有class了)所以靠构造函数模拟;没有继承的概念所以靠原型链模拟。。总之为了保证简单,去掉了复杂的概念,然后用简单的东西再去模拟复杂的东西。prototype则主要用在继承的实现上。

  在用构造函数构造对象时,有一个显而易见的缺点 – 无法共享属性和方法。构造出的对象每个都是相互独立的,既不互相影响,也都有自己属性和方法的副本,没有共享可言,也浪费了资源。为了解决这个问题,每个函数都有一个prototype属性,这里记录了 所有实例对象需要共享的属性和方法,而那些不需要共享的数据或者方法,则还放在构造函数中去。而实例对象一经创建,自动引用prototype中记录的属性和方法。

function Dog (name, age) {
    this.name = name;
    this.age = age;
}
Dog.prototype.class = '犬科';
Dog.prototype.sayProperty = function () {
    console.log('单身狗');
}
var xcola = new Dog(xcola, 23);
var cola = new Dog(cola, 23);
xcola.sayProperty();		// 单身狗
cola.class;			// 犬科

  prototype记录的方法和属性为所有实例所共享,而prototype一经改动,变化也会反映在所有实例中。所以看起来,prototype像是实例对象的原型,实例则像是继承了prototype对象。

方法

  函数有两个相似的方法 – call和apply。不仅作用相似,使用方法也很相似。接下来是一段引用,看完是有点茅塞顿开的感觉。。

每个函数都包涵两个非继承而来的方法:apply()和call()。这两个方法的用途都是在特定的作用域中调用函数。实际上等于设置函数体内的this对象的值。

  如果死记硬背call和apply的作用是改变函数中this的值,可能不能领会到在程序中使用call和apply函数的真正意图。

  call()的第一个参数是this指针的指向,其余参数则为要传给函数的参数。如下代码将func函数内部的this指针指向了window对象,a和b则是传给func函数的参数。而apply()与call()的区别仅仅是在于,传给函数的是参数还是参数数组。然而如果func函数需要的参数是arguments,此时显然apply函数更为恰当。

func.call(window,a,b)
func.apply(window,[a,b])

  引自javascript高级程序设计,call和apply真正强大的地方在于扩展函数赖以运行的作用域。通过这两种方法,可以使得对象获得指定对象的属性和方法。

function Person(name , age){
    this.name = name;
    this.age = age;
    this.sayHello = function () {
    	console.log('hi danshengou')
	}
}
function Student(name ,age ,score){
    Person.call(this, name, age);
    this.score = score;
}
var s = new Student('cola', 23, 60);
s.sayHello();		// hi danshengou
s.name;				// cola
s.age;				// 23
s.score;			// 60

从网上借鉴来一种运用场景:通过document.getElementsByTagName

选择的dom节点是一种类似array的伪数组。但它不能应用Array下的push,pop等方法。于是可以通过:
var domNodes = Array.prototype.slice.call(document.getElementsByTagName("*"));
这样domNodes就可以应用Array下的所有方法了。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

对闭包的理解 Previous