변수 선언 승격, 프로토타입, 이 포인터

高洛峰
풀어 주다: 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이라는 정적 속성을 만들어서 이 질문의 전반부를 살펴보겠습니다. 익명 함수를 만든 다음 Foo의 프로토타입 개체에 대해 getName이라는 새 익명 함수를 만듭니다. 그런 다음 함수 변수 표현식을 통해 getName 함수가 생성되고, 마지막으로 getName 함수가 선언됩니다.

첫 번째 질문인 Foo.getName은 당연히 Foo 함수에 저장된 정적 속성에 액세스하는 것이므로 당연히 2입니다. 말할 것도 없습니다.

두 번째 질문

두 번째 질문, getName 함수를 직접 호출합니다. 직접 호출되기 때문에 위의 현재 범위에서 getName이라는 함수에 접근하고 있으므로 1 2 3과는 아무런 관련이 없습니다. 여기에는 두 가지 함정이 있습니다. 하나는 변수 선언 승격이고 다른 하나는 함수 표현식입니다.

변수 선언 승격

즉, 선언된 모든 변수나 선언된 함수가 현재 함수의 상위로 승격됩니다.

예를 들어 다음 코드는:

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가 이 코드를 두 줄의 코드로 나누어 별도로 실행한다는 것입니다.

예를 들어 다음 코드는 다음과 같습니다.

console.log(x);//输出:function x(){}
var x=1;
function x(){}
로그인 후 복사

실제 실행되는 코드는 먼저 var x=1을 두 줄로 분할한 다음, var x; 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
로그인 후 복사

세 번째 질문

세 번째 질문 Foo().getName(); Foo 함수가 실행된 후 Foo 함수의 반환값 객체의 getName 속성 함수가 호출됩니다.

Foo 함수 getName = function () { console.log(1) }; 의 첫 번째 문장은 var 선언이 없으므로 먼저 getName을 찾으세요. 현재 Foo 함수 범위의 변수입니다. 그런 다음 현재 함수 범위의 상위 계층, 즉 외부 범위를 살펴보고 getName 변수가 포함되어 있는지 확인합니다. 두 번째 질문에서 이 값을 할당합니다. 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 학습자의 빠른 성장을 도와주세요!