本文深入解析JavaScript函数的定义方式、执行机制及原型链原理。详细对比函数声明与表达式的提升差异,阐述prototype与__proto__的关系,并讲解实例方法与静态方法的实现。同时涵盖闭包、apply/call调用方式及this指向规则,帮助开发者掌握JS核心概念。
一、定义
在JavaScript中,函数可以通过两种主要方式进行创建:一种是函数声明式,另一种是函数表达式。
(1)函数声明式:
需要注意的是,分号在JavaScript中用于分隔可执行语句。由于函数声明本身不属于可执行语句,因此其定义末尾不需要也不应该加分号。
function a(){
console.log('bbb');
}(2)函数表达式:
与声明式不同,函数表达式被视为一条执行语句,因此必须以分号结尾。
var a = function(){
console.log('aaa');
}(3)声明式变体:
var a = function a(){
console.log('bbb');
}这种写法在功能上完全等价于标准的 `function b(){}` 声明方式。
二、函数执行
JavaScript引擎在解析代码时,会将当前作用域内声明的所有变量和函数名提前至作用域的顶部。然而,只有变量的声明会被提升,其赋值操作仍保留在代码的原始位置。
示例1:
var a = function(){
console.log('aaa');
}
function a(){
console.log('bbb');
}
a(); //结果:aaa示例2:
(function() {
console.log(noSuchVariable);
})();
//报错,因为noSuchVariable变量根本没定义</pre><pre name="code" class="javascript">(function() {
console.log(declaredLater); //"undefined"
var declaredLater = "Now it's defined!";
console.log(declaredLater); //"Now it's defined!"
})();
//上面这段代码是正确的,没有任何问题。示例3:
var a; // 声明一个变量,标识符为a
function a() {} // 声明一个函数,标示符也为a
alert(typeof a); // function
function a() {}
var a;
alert(typeof a); //function
function a() {}
var a = 1;
alert(typeof a); //number
var a = 1;
function a() {}
alert(typeof a); //number总结:
- 变量的声明会被提升至作用域顶部,但其赋值操作保留在原处;
- 函数声明式的整个定义会被提升至作用域顶部;
- 对于函数表达式,只有变量名被提升,函数体并未被提升;
- 函数提升的优先级高于变量提升,若存在重复的 `var` 声明,后续的声明会被忽略;
- 当变量名与函数名相同时,变量先被提升,随后函数声明会覆盖该变量(即函数定义生效);
- 最终的赋值结果取决于代码执行的先后顺序,后执行的赋值会覆盖之前的值。
三、函数是对象[typeof instanceof]
在JavaScript中,使用 `typeof` 操作符检测函数类型时,返回值为字符串 "function"。但从概念上讲,将JavaScript函数视为一种特殊的对象更为准确,因为函数拥有自己的属性和方法。
(1)typeof区分对象和其他原始值
JavaScript的数据类型主要分为两类:原始类型(包括数字、字符串、布尔值、null和undefined)和对象类型。
`typeof` 操作符对任何类型进行判断,其返回值包括:boolean、string、number、object、undefined 以及 function。
typeof 2; //'number'
typeof '2'; //'string'
typeof true; //'boolean'
typeof []; //'object'
typeof null; //'object'
typeof undefined; //'undefined'
var a = function(){}; typeof a; //'function'对于所有内置的可执行对象,`typeof` 一律返回 "function"。
typeof new Number(2); //'object'
typeof new String('2'); //'object'
typeof new Boolean(true); //'object'(2)instanceof[对象 instanceof 类]判断一个对象是否为类的实例
[] instanceof Object; //true
2 instanceof Number; //false
new Number(2) instanceof Number; //true示例:
var a = function(){};
var b = function(){};
a.prototype = new b();
new a() instanceof b; //true
new a() instanceof Object; //true判断逻辑是:首先确定 `b.prototype`,如果 `b.prototype` 存在于对象 `a` 的原型链中,则返回 true,否则返回 false。
需要注意的是,JavaScript中所有的对象都是 `Object` 的实例。
(3)扩展 `Object.prototype.toString.call([]) === '[object Array]'`; 这是一种更严谨的类型判断方式。
四、prototype
JavaScript对象拥有两种属性:一种是对象自身的属性,另一种是通过原型链继承而来的属性。
在JavaScript中,类的每个实例对象都从同一个原型对象上继承属性,因此原型对象是类的核心组成部分。
(1)只有函数拥有 `prototype` 属性,普通对象没有。
var b = {};
b.prototype.bbb='bbb'; //报错b.prototype为undefined
function b(){}
b.prototype.bbb='bbb';
console.log(b); //结果:function b(){}
console.log(b.prototype); //结果:b {bbb: "bbb"}
console.log(b.bbb); //结果:undefined
console.log(new b().bbb); //结果:bbb(2)`prototype` 是函数的内置属性,用于设置函数的原型;而 `__proto__` 是对象的内置属性,用于设置对象的原型。
PS:注意严格区分函数和对象的概念。
function test(){}
test.prototype; //结果:test {}
new test().prototype; //结果:undefined,对象没有prototype
new test().__proto__; //结果:test {}
function a(){
this.v='vvv';
}
a.prototype; //a {}
new a.__proto__; //a {}
new a().__proto__ === a.prototype; //true 实例对象的__proto__,指向函数的原型(3)每个JavaScript函数都自动拥有一个 `prototype` 属性。该属性的值是一个对象,这个对象包含一个唯一的不可枚举属性 `constructor`。`constructor` 属性的值是一个指向该函数的引用。
function a(){
this.v='vvv';
}
a.prototype.constructor === a; //true[物理地址一样]
a.prototype.constructor; //constructor属性指代这个类
a.prototype.constructor.__proto__ === a.__proto__ === Function.prototype; //true
Object.prototype.constructor.__proto__ === Object.__proto__ === Function.prototype; //truePS:所有构造器和函数的 `__proto__` 都指向 `Function.prototype`,它是一个空函数(Empty function)。
(4)实例
示例1:
function a(){}
function b(){}
a.prototype = {aaa:'aaa'};
b.prototype = a.prototype;
var aa1 = new a();
var bb1 = new b();
//aa1.prototype.aaa='xxx'; //错误,对象没有原型
a.prototype.aaa = 'xxx';
console.log( aa1.aaa ); //xxx
console.log( bb1.aaa ); //xxx补充:`a===b; //false`;`a.prototype === b.prototype; //true`
实例2:
function a(){}
function b(){ this.bbb='bbb'}
a.prototype = new b(); //相当于a.prototype = {bbb: "bbb"};
console.log(new a().bbb ); //结果:bbb补充:`a.prototype === b.prototype; //false`
五、实例方法&静态方法
(1)实例方法
function a(){}
a.prototype.count = 0;
var a1 = new a();
a1.count++;
var a2 = new a();
a2.count++;
a.prototype.count; //0PS:定义实例方法的常见形式为 `a.prototype.method = function(){}`。
(2)静态方法
function a(){}
a.count = 0;
new a().count(); //undefined,只能使用a.count调用PS:定义静态方法的常见形式为 `a.method = function(){}`。
(3)当执行代码 `var p = new Person()` 时,`new` 关键字会执行以下操作:
A. 创建一个空白对象;
B. 创建一个指向 `Person.prototype` 的指针;
C. 将这个对象通过 `this` 关键字传递给构造函数,并执行构造函数。
六、闭包
函数定义时所处的作用域链,在函数执行时依然有效,这就是闭包的核心机制。
示例-典型闭包:
var scope = "global scope";
function checkScope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkScope(); //local scope示例:更为常用的闭包场景。
function counter(){
var n=0;
return{
count:function(){
return ++n;
},
reset:function(){
n=0;
}
};
}
var c1 = new counter();
c1.count(); //1
var c2 = new counter();
c2.count(); //1
c1.reset(); //c1置为0,c2不受影响
c2.count(); //2PS:不同的实例对象之间互不影响,但单个实例对象内的方法可以共享私有变量。
七、通过调用方法的形式来间接调用函数:apply&call
function a(){
console.log(this.x+this.y);
}
a.call({x:100,y:100}); //200
function C(){
this.m = function(){
console.log(this.x+this.y);
}
}
var c = new C();
c.m.call({x:100,y:100}); //200`call` 和 `apply` 的第一个参数指定了函数执行时的上下文对象,该对象将变为函数内部的 `this` 值。
语法:`函数.call(对象,参数1,参数2);`
语法:`函数.apply(对象,[参数1,参数2]);` // 参数以数组形式传递
补充:
function a(a,b,c,d){
//将伪数组转变为真数组
var args = [].slice.call(arguments);
}八、函数中this
function a(){
console.log(this); //a {}
function b(){
console.log(this); //window
}
b();
}
new a();特别注意:在非严格模式下,函数内部嵌套的函数其 `this` 默认指向全局对象 window。
此问题在严格模式中已被修正;在非严格模式中,可以使用一个变量来接收外部函数的 `this` 供内部函数使用,例如:`var that = this;`。
九、回调(函数当参数)
function a(callback){
callback();
}
a(function(){
console.log('aaa');
});回调机制更加灵活,主页面只需为子页面提供对应的接口,具体的处理逻辑完全由子页面自行处理。
到此这篇关于JavaScript函数及其prototype详解的文章就介绍到这了,更多相关JavaScript函数及其prototype内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!