JavaScript Learning record day6-function variable scope, deconstruction assignment and method
@ (learning) [JavaScript]
[TOC]
1. Scope
In JavaScript, variables that are var
declared are actually scoped.
If a variable is declared inside the function body, the variable is scoped to the entire function body, and the variable is not referenced outside the body of the function:
‘use strict‘;function foo() { var x = 1; x = x + 1;}x = x + 2; // ReferenceError: x is not defined 无法在函数体外引用变量x
If two different functions declare the same variable, the variable works only in the body of the function. In other words, the same name variables inside different functions are independent from each other:
function foo() { var x = 1; x = x + 1; console.log(x);}function bar() { var x = ‘A‘; x = x + ‘B‘; console.log(x);}foo(); // 2bar(); // AB
Because JavaScript functions can be nested, an intrinsic function can access variables defined by an external function, which in turn does not:
function foo() { var x = 1; function bar() { var y = x + 1; // bar可以访问foo的变量x } var z = y + 1; // ReferenceError: y is not defined, foo不可以访问bar的变量y}foo();
What if the names of the variables for the intrinsic and external functions are identical? To test:
‘user strict‘;function foo() { var x = 1; function bar() { var x = ‘A‘; console.log(‘x in bar() = ‘ + x); } console.log(‘x in foo() = ‘ + x); bar();}foo();// 结果:// x in foo() = 1// x in bar() = A
This means that JavaScript functions look for variables when they are found, starting with their own function definitions, and from "inside" to "outside". If an intrinsic function defines a variable with the same name as an external function, the variable of the intrinsic function will "mask" the variable of the outer function.
2. Variable Promotion
JavaScript's function definition has a feature that scans the entire function body's statement and "promotes" all declared variables to the top of the function:
‘use strict‘;function foo() { var x = ‘Hello, ‘ + y; console.log(x); var y = ‘Bob‘;}foo();
Although it is a strict
pattern, the statement var x = ‘Hello, ‘ + y;
does not error because the variable is y
declared later. But console.log
shown Hello, undefined
, y
the value of the description variable is undefined
. This is precisely because the JavaScript engine automatically promotes the declaration of the variable y, but does not raise y
the value of the variable.
For the above foo()
functions, the JavaScript engine sees the code equivalent to:
function foo() { var y; // 提升变量y的申明,此时y为undefined var x = ‘Hello, ‘ + y; console.log(x); y = ‘Bob‘;}
Because of this bizarre "feature" of JavaScript, when we define variables inside a function, strictly abide by the "first declaration of all variables within a function" rule. The most common practice is to declare all the variables used inside the function with a var:
function foo() { var x = 1, // x初始化为1 y = x + 1, // y初始化为2 z, i; // z和i为undefined // 其他语句: for (i=0; i<100; i++) { ... }}
3. Global scope
Variables that are not defined within any function have global scope. In fact, JavaScript has a global object by default window
, and the global scope variable is actually bound to window
one of the properties:
‘use strict‘;var course = ‘Learn JavaScript‘;console.log(course); // ‘Learn JavaScript‘console.log(window.course); // ‘Learn JavaScript‘
Therefore, direct access to global variables course
and access window.course
is exactly the same.
Because a function definition has two ways, a function defined as a variable is var foo = function () {}
actually a global variable, so the definition of the top-level function is also treated as a global variable and bound to the window
object:
‘use strict‘;function foo() { alert(‘foo‘);}foo(); // 直接调用foo()window.foo(); // 通过window.foo()调用
The alert () function that we call each time is actually a variable of window:
‘use strict‘;window.alert(‘调用window.alert()‘);// 把alert保存到另一个变量:var old_alert = window.alert;// 给alert赋一个新函数:window.alert = function () {}alert(‘无法用alert()显示了!‘);// 恢复alert:window.alert = old_alert;alert(‘又可以用alert()了!‘);
This means that JavaScript actually has only one global scope. Any variable (the function is also considered a variable), if not found in the current function scope, will continue to look up, and finally, if not found in the global scope, the ReferenceError
error is reported.
4. Name space
Global variables are bound to window
, and different JavaScript files that use the same global variables, or the top-level functions that define the same name, can cause naming conflicts and are difficult to find.
One way to reduce the conflict is to bind all of your variables and functions to a global variable. For example:
// 唯一的全局变量MYAPP:var MYAPP = {};// 其他变量:MYAPP.name = ‘myapp‘;MYAPP.version = 1.0;// 其他函数:MYAPP.foo = function () { return ‘foo‘;};
Putting all your own code into a unique namespace MYAPP
can greatly reduce the likelihood of global variable collisions.
Many well-known JavaScript libraries are doing this: Jquery,yui,underscore and so on.
5. Local scope
Since the variable scope of JavaScript is actually inside the function, we for
cannot define variables with local scope in the block of statements such as loops:
‘use strict‘;function foo() { for (var i=0; i<100; i++) { // } i += 100; // 仍然可以引用变量i}
To solve the block-level scope, ES6 introduces a new keyword let
, which let
can be used instead var
to declare a block-scoped variable:
‘use strict‘;function foo() { var sum = 0; for (let i=0; i<100; i++) { sum += i; } // SyntaxError: i += 1;}
6. Constants
Because var
and let
declaration is a variable, if you want to declare a constant, before ES6 is not possible, we usually use all uppercase variables to represent "This is a constant, do not modify its value":
var PI = 3.14;
The ES6 Standard introduces new keywords const
to define constants, const
and let
both have block-level scopes:
‘use strict‘;const PI = 3.14;PI = 3; // 某些浏览器不报错,但是无效果!PI; // 3.14
7. Deconstruction Assignment
Starting with ES6, JavaScript introduces an understanding of construct assignments, which can be assigned to a set of variables at the same time.
What is a deconstructed assignment? Let's take a look at the traditional way of assigning the elements of an array to several variables individually:
var array = [‘hello‘, ‘JavaScript‘, ‘ES6‘];var x = array[0];var y = array[1];var z = array[2];
Now, in ES6, you can assign values directly to multiple variables using a deconstructed assignment:
‘use strict‘;// 如果浏览器支持解构赋值就不会报错:var [x, y, z] = [‘hello‘, ‘JavaScript‘, ‘ES6‘];// x, y, z分别被赋值为数组对应元素:console.log(‘x = ‘ + x + ‘, y = ‘ + y + ‘, z = ‘ + z);
<font color= "Red" > note </font>:
When an array element is deconstructed and assigned, multiple variables are [...]
enclosed.
If the array itself is nested, you can also use the following form to deconstruct the assignment, noting that the nesting level and position should be consistent:
let [x, [y, z]] = [‘hello‘, [‘JavaScript‘, ‘ES6‘]];x; // ‘hello‘y; // ‘JavaScript‘z; // ‘ES6‘
Deconstruction assignments can also omit certain elements:
let [, , z] = [‘hello‘, ‘JavaScript‘, ‘ES6‘]; // 忽略前两个元素,只对z赋值第三个元素z; // ‘ES6‘
If you need to remove several properties from an object, you can also use a deconstructed assignment to quickly get the specified properties of an object:
‘use strict‘;var person = { name: ‘小明‘, age: 20, gender: ‘male‘, passport: ‘G-12345678‘, school: ‘No.4 middle school‘};var {name, age, passport} = person;// name, age, passport分别被赋值为对应属性:console.log(‘name = ‘ + name + ‘, age = ‘ + age + ‘, passport = ‘ + passport);
Sometimes, if a variable has been declared and assigned again, the correct wording will also report a syntax error:
// 声明变量:var x, y;// 解构赋值:{x, y} = { name: ‘小明‘, x: 100, y: 200};// 语法错误: Uncaught SyntaxError: Unexpected token =
This is because the JavaScript engine treats the statement beginning with {as a block, so = is no longer legal. The workaround is to enclose them in parentheses:
({x, y} = { name: ‘小明‘, x: 100, y: 200});
Deconstruct assignment Usage Scenarios
Deconstructed assignments can greatly simplify code in many cases. For example, to exchange two variables x
and y
values, you can write this and no longer need temporary variables:
var x=1, y=2;[x, y] = [y, x]
Quick access to the current page's domain name and path:
var {hostname:domain, pathname:path} = location;
If a function receives an object as a parameter, you can use deconstruction to bind the object's properties directly to the variable. For example, the following function can quickly create an Date
object:
function buildDate({year, month, day, hour=0, minute=0, second=0}) { return new Date(year + ‘-‘ + month + ‘-‘ + day + ‘ ‘ + hour + ‘:‘ + minute + ‘:‘ + second);}
Its convenience is that the incoming object only needs year
, month
and day
these three properties:
buildDate({ year: 2017, month: 1, day: 1 });// 2017-12-31T16:00:00.000Z (UTC)
You can also pass in hour
, minute
and second
properties:
buildDate({ year: 2018, month: 1, day: 1, hour: 20, minute: 15 });// 2018-01-01T12:15:00.000Z (UTC)
Using a deconstructed assignment can reduce the amount of code, but it needs to work correctly in a modern browser that supports the ES6 deconstruction assignment feature. Currently, browsers that support deconstruction assignments include Chrome,firefox,edge, and so on.
8. Methods
A method that binds a function in an object called this object.
In JavaScript, the definition of an object is this:
var xiaoming = { name: ‘小明‘, birth: 1990};
However, if we xiaoming
bind a function, we can do more things. For example, write a age()
method that returns xiaoming
the age:
var xiaoming = { name: ‘小明‘, birth: 1990, age: function () { var y = new Date().getFullYear(); return y - this.birth; }};console.log(xiaoming.age); // [Function: age]console.log(xiaoming.age()); // 当前调用是28,下一年调用就变成29了
A function that is bound to an object is called a method, and it is no different from a normal function, but it uses a this
keyword internally.
Inside a method this
is a special variable that always points to the current object, which is xiaoming
the variable. So, this.birth
you can get xiaoming
the birth
properties.
Let's take it apart and write:
function getAge() { var y = new Date().getFullYear(); return y - this.birth;}var xiaoming = { name: ‘小明‘, birth: 1990, age: getAge};console.log(xiaoming.age()); // 25, 正常结果console.log(getAge()); // NaN
If the function inside JavaScript is called this
, then this
who does this point to?
The answer is, the situation is set!
If called as a method of an object, for example xiaoming.age()
, this function's this point points to the object being called, that xiaoming
is, this is in line with our expectations.
If a function is called individually, for example, getAge()
at this point, the function points to the this
global object, that is window
.
If this is the case:
var fn = xiaoming.age; // 先拿到xiaoming的age函数fn(); // NaN
It's not going to work! To ensure that this
the point is correct, it must be obj.xxx()
called in the form!
Since this is a huge design error, it's not that simple to correct. The ECMA decides to strict
make the function point in the pattern this
undefined
so that, in the strict
mode, you get an error:
‘use strict‘;var xiaoming = { name: ‘小明‘, birth: 1990, age: function () { var y = new Date().getFullYear(); return y - this.birth; }};var fn = xiaoming.age;fn(); // TypeError: Cannot read property ‘birth‘ of undefined
The decision was to expose the error in time and did not solve this
the correct position to point to.
There are times when you refactor a method like refactoring:
‘use strict‘;var xiaoming = { name: ‘小明‘, birth: 1990, age: function () { function getAgeFromBirth() { var y = new Date().getFullYear(); return y - this.birth; } return getAgeFromBirth(); }};xiaoming.age(); // TypeError: Cannot read property ‘birth‘ of undefined
The result is another error! The reason is that the this
pointer is age
pointing only inside the function of the method, the xiaoming
function defined inside the function, and this
pointing again undefined
! (in non- strict
modal, it points back to the global object window
!) )
There is no way to fix it, we use a that
variable first to capture this
:
‘use strict‘;var xiaoming = { name: ‘小明‘, birth: 1990, age: function () { var that = this; // 在方法内部一开始就捕获this function getAgeFromBirth() { var y = new Date().getFullYear(); return y - that.birth; // 用that而不是this } return getAgeFromBirth(); }};console.log(xiaoming.age()); // 28
var that = this;
, you can safely define other functions within the method, rather than piling all the statements into a single method.
9.
apply
Although in a separate function call, depending on whether it is a strict
pattern, this
point to undefined
or window
, however, we can still control this
the point!
To specify this
which object the function points to, you can use the method of the function itself, apply
which receives two arguments, the first parameter is the variable that needs to be bound, and the this
second argument is the Array
argument that represents the function itself.
apply
Call with repair getAge()
:
function getAge() { var y = new Date().getFullYear(); return y - this.birth;}var xiaoming = { name: ‘小明‘, birth: 1990, age: getAge};console.log(xiaoming.age()); // 28console.log(getAge.apply(xiaoming, [])); // 28, this指向xiaoming, 参数为空
Another way to do apply()
This is call()
that the only difference is:
apply()
Package the parameters into a Array
re-incoming;
call()
The parameters are passed in order.
such as calls Math.max(3, 5, 4)
, respectively, apply()
and call()
implemented as follows:
console.log(Math.max.apply(null, [3, 5, 4])); // 5console.log(Math.max.call(null, 3, 5, 4)); // 5
For normal function calls, we usually this
bind to null
.
10. Decorative Device
Using apply()
, we can also dynamically change the behavior of the function.
All JavaScript objects are dynamic, even if built-in functions, we can also re-point to the new function.
Now suppose we want to count how many times the code has been called parseInt()
, to find out all the calls, and then add them manually count += 1
, but that's silly. The best solution is to replace the default with our own function parseInt()
:
‘use strict‘;var count = 0;var oldParseInt = parseInt; // 保存原函数function parseInt() {}window.parseInt = function () { count += 1; return oldParseInt.apply(null, arguments); // 调用原函数};// 测试:parseInt(‘10‘);parseInt(‘20‘);parseInt(‘30‘);console.log(‘count = ‘ + count); // 3
Learning Reference Tutorial: http://www.liaoxuefeng.com
JavaScript Learning record day6-function variable scope, deconstruction assignment and method