変数宣言のプロモーション、プロトタイプ、このポインタ

高洛峰
リリース: 2016-11-21 15:04:07
オリジナル
982 人が閲覧しました

質問は次のとおりです:

function Foo() {
    getName = function () { console.log(1); };
    return this;
}
Foo.getName = function () { console.log(2);};
Foo.prototype.getName = function () { console.log(3);};
var getName = function () { console.log(4);};
function getName() { console.log(5);}

//请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
ログイン後にコピー

答えは次のとおりです:

function Foo() {
    getName = function () { console.log(1); };
    return this;
}
Foo.getName = function () { console.log(2);};
Foo.prototype.getName = function () { console.log(3);};
var getName = function () { console.log(4);};
function getName() { console.log(5);}

//答案:
Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName();//3
ログイン後にコピー

この質問には、変数宣言のプロモーション、このポインタのポインティング、演算子の優先順位、プロトタイプ、継承、グローバル変数の汚染、オブジェクト属性とプロトタイプ属性の優先順位など、多くの知識ポイントが含まれます。等

この質問には 7 つの質問が含まれています。以下で説明してください。

最初の質問

まず、この質問の前半で行われたことを見てみましょう。まず、Foo という関数が定義され、Foo に匿名関数を格納するための静的プロパティが作成されました。オブジェクトには、getName という名前の新しく作成された匿名関数があります。次に、関数変数式を通じて getName 関数が作成され、最後に getName 関数が宣言されます。

Foo.getName に関する最初の質問は、当然のことながら、Foo 関数に格納されている静的プロパティにアクセスすることです。これは当然 2 です。言うことはありません。

2 番目の質問

2 番目の質問は、getName 関数を直接呼び出します。直接呼び出しているため、上記の現在のスコープにある getName という関数にアクセスしているため、1 2 3 とは関係ありません。ここには 2 つの落とし穴があります。1 つは変数宣言の昇格、もう 1 つは関数式です。

変数宣言の昇格

つまり、宣言されたすべての変数または宣言された関数は、現在の関数の先頭に昇格されます。

たとえば、次のコード:

console.log(‘x’ in window);//true
var x;
ログイン後にコピー
x = 0;
ログイン後にコピー

コードが実行されると、JS エンジンは宣言ステートメントをコードの先頭に上げて次のようになります:

var x;
console.log(‘x’ in window);//true
x = 0;
ログイン後にコピー

関数式

var getName と function getName は両方とも宣言ですステートメントとの違いは、 var getName が関数式であり、 function getName が関数宣言であることです。

関数式の最大の問題は、js がこのコードを 2 行のコードに分割し、別々に実行することです。

たとえば、次のコード:

console.log(x);//输出:function x(){}
var x=1;
function x(){}
ログイン後にコピー

実際に実行されるコードは次のとおりです: まず var x=1 を var x; と x = 1; の 2 行に分割し、次に 2 つの行 var x と function x( ){} 先頭は次のようになります:

var x;
function x(){}
console.log(x);
x=1;
ログイン後にコピー

つまり、最後の関数で宣言された x は変数で宣言された x をカバーし、ログ出力は x 関数になります。

同様に、元の質問のコードの最終実行は次のとおりです:

function Foo() {
    getName = function () { console.log(1); };
    return this;
}
var getName;//只提升变量声明
function getName() { console.log(5);}//提升函数声明,覆盖var的声明

Foo.getName = function () { console.log(2);};
Foo.prototype.getName = function () { console.log(3);};
getName = function () { console.log(4);};//最终的赋值再次覆盖function getName声明

getName();//最终输出4
ログイン後にコピー

3 番目の質問

3 番目の質問 Foo().getName(); は、最初に Foo 関数を実行し、次に、その戻り値オブジェクトを呼び出します。 Foo関数のgetName属性関数。

Foo 関数 getName = function () { console.log(1) }; の最初の文は関数代入ステートメントであるため、最初に現在のファイル内で getName 変数を探します。 Foo 関数のスコープはありません。次に、現在の関数スコープの上位層、つまり外側のスコープを調べて、getName 変数が含まれているかどうかを確認します。これは、2 番目の質問のalert(4) 関数です。 function(){alert(1) } への変数。

外側のスコープの getName 関数はここで実際に変更されます。

注: ここでまだ見つからない場合は、ウィンドウ オブジェクトまで検索します。ウィンドウ オブジェクトに getName 属性がない場合は、ウィンドウ オブジェクトに getName 変数を作成します。

之后Foo函数的返回值是this,而JS的this问题博客园中已经有非常多的文章介绍,这里不再多说。

简单的讲, this的指向是由所在函数的调用方式决定的 。而此处的直接调用方式,this指向window对象。

遂Foo函数返回的是window对象,相当于执行 window.getName() ,而window中的getName已经被修改为alert(1),所以最终会输出1

此处考察了两个知识点,一个是变量作用域问题,一个是this指向问题。

第四问

直接调用getName函数,相当于 window.getName() ,因为这个变量已经被Foo函数执行时修改了,遂结果与第三问相同,为1

第五问

第五问 new Foo.getName(); ,此处考察的是js的运算符优先级问题。

js运算符优先级:

参考链接: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

通过查上表可以得知点.的优先级高于new操作,遂相当于是:

new (Foo.getName)();
ログイン後にコピー

所以实际上将getName函数作为了构造函数来执行,遂弹出2。

第六问

第六问 new Foo().getName() ,首先看运算符优先级()高于new,实际执行为

(new Foo()).getName()
ログイン後にコピー

遂先执行Foo函数,而Foo此时作为构造函数却有返回值,所以这里需要说明下js中的构造函数返回值问题。

构造函数的返回值

在传统语言中,构造函数不应该有返回值,实际执行的返回值就是此构造函数的实例化对象。

而在js中构造函数可以有返回值也可以没有。

1、没有返回值则按照其他语言一样返回实例化对象。

function F(){}
new F()
//>F {}
ログイン後にコピー

2、若有返回值则检查其返回值是否为 引用类型 。如果是非引用类型,如基本类型(string,number,boolean,null,undefined)则与无返回值相同,实际返回其实例化对象。

function F(){return 1;}
new F()
//>F {}
ログイン後にコピー

原题中,返回的是this,而this在构造函数中本来就代表当前实例化对象,遂最终Foo函数返回实例化对象。

之后调用实例化对象的getName函数,因为在Foo构造函数中没有为实例化对象添加任何属性,遂到当前对象的原型对象(prototype)中寻找getName,找到了。

遂最终输出3。

第七问

第七问, new new Foo().getName(); 同样是运算符优先级问题。

最终实际执行为:

new ((new Foo()).getName)();
ログイン後にコピー

先初始化Foo的实例化对象,然后将其原型上的getName函数作为构造函数再次new。

遂最终结果为3


関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!