面向对象编程 和 函数 - 复习
2018-6-28复习
每一次回头看基础,都有新的理解
Function.prototype.call
函数实例的call方法,可以指定函数内部this的指向(即:函数执行时所在的作用域,--- 函数运行时所在的对象 )
,然后在所指定的作用域中,调用该函数。
注意: call方法是函数的方法
应用:
- 将 ( 类似数组的对象 ) 转换成 ( 数组 )
(1)
[].slice.call(arguments)
等于 Array.prototype.slice.call(arguments)
等于 Array.from(arguments)
--- [].slice.call(arguments, x,y)参数x,y是slice()方法执行时传入的参数
(2)
slice()方法 ----- 截取数组的一部分,返回截取部分的新数组,不改变原数组
slice(start, end)方法,截取数组的一部分,start是启始位置,end是终止位置(不包含)
数组的slice()方法不加参数时,等于数组的拷贝
实例:
Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 }) // ['a', 'b']
--- 将slice()方法中的this绑定在参数对象上,并在参数对象的作用域内执行slice()方法
函数实例的call方法,可以指定函数内部this的指向(即:函数执行时所在的作用域,--- 函数运行时所在的对象 )
,然后在所指定的作用域中,调用该函数。
- 函数实例的call方法,可以指定函数内部this的指向,指定函数运行时所在的对象,然后在指定的作用域中,执行该函数。
- call方法会执行函数,无需手动调用
- call方法的参数是一个对象,如果参数是空,null,undefined,则默认传入全局对象window
- call方法可以接受多个参数 :
第一个参数是this所要指向的对象。
后面的参数是函数调用时,所需的参数
var obj = {};
var f = function () {
return this;
};
f() === window // true
f.call(obj) === obj // true
--- 把f函数的this绑定在obj对象上,并在obj的作用域中,执行f函数
var n = 123;
var obj = { n: 456 };
function a() {
console.log(this.n);
}
a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456
--- 参数是null,空,undefined时,默认传入的是全局对象 window
Function.prototype.apply()
apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数
- apply()方法也是改变方法中this的执行,和call()相同
- apply() 与 call() 方法不同的是,apply接收的参数是一个数组
- apply()当第一个参数是null,空,undefined时,默认传入的是全局对象window
func.apply(ojb, [arg1, arg2, ...])
apply()方法:
第一个参数,是this所要绑定的对象,this所要指向的对象
第二个参数是一个数组,会将成员依次作为函数调用时的参数
function f(x, y){
console.log(x + y);
}
f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2
解析:
f()函数,本来是接受两个参数,但在使用apply方法后,就可以变成,接受一个数组为参数
应用:
- 找出数组中最大的数---(或者最小的数)
Math.max(1,2,5,3) // 5
Math.min(1,2,3) // 1
const arr = [1,6,2,3,4,5];
Math.max.apply(null, arr) // 6 将max方法中的this指向顶层对象,参数是arr数组
等于:Math.max.apply(undefined, arr)
等于:Math.max([...arr])
- 将数组的 ( 空元素 ) 变成 ( undefined )
- 通过apply方法,利用Array构造函数将数组的空元素变成undefined。
空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined。因此,遍历内部元素的时候,会得到不同的结果。
Array.apply(null, ['a', ,'b'])
等于: [...['a', , 'b']]
// [ 'a', undefined, 'b' ]
- 转换类似数组的对象
- 现在有了展开运算符,apply方法的各种运用都可以用展开运算符或者其他es6语法代替
Function.prototype.bind()
bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。
-
bind方法
不主动执行函数 -
call方法
和apply方法
都在绑定函数this指向的对象后,在指向对象的作用域内主动执行该函数 - bind方法的参数,就是所要绑定的this对象
- bind可以接收多个参数,第一个参数是要绑定的this的对象,其他参数是原函数的参数
- 如果bind方法的第一个参数是null或undefined,等于将this绑定到全局对象,函数运行时this指向顶层对象(浏览器为window)
var counter = {
count: 0,
inc: function () {
this.count++;
}
};
var func = counter.inc.bind(counter);
func();
counter.count // 1
解析:
1.
将counter.inc方法中的this绑定在counter对象上
如果不绑定,var func = counter.inc将该方法赋值给变量后,this指向了顶层对象window
var add = function (x, y) {
return x * this.m + y * this.n;
}
var obj = {
m: 2,
n: 2
};
var newAdd = add.bind(obj, 5); // add函数的第一个参数
newAdd(5) // 20 // add函数的第二个参数
bind函数注意点
- bind方法每运行一次,就返回一个新函数。
- 结合回调函数使用 -------------( 重要 )
回调函数是 JavaScript 最常用的模式之一,但是一个常见的错误是,将包含this的方法直接当作回调函数。解决方法就是使用bind方法,将counter.inc绑定counter。
- 不能将含有this的方法直接当做回调函数。而应该用bind方法,将this绑定给原对象上
var counter = {
count: 0,
inc: function () {
'use strict';
this.count++;
}
};
function callIt(callback) {
callback();
}
callIt(counter.inc.bind(counter));
counter.count // 1
构造函数的缺点
同一个构造函数的多个实例之间,无法共享属性,从而造成对系统资源的浪费。
-
构造函数内部,通过this定义的属性,在使用new命令生成实例对象的时候,这些属性会定义在实例对象上,即 hasOwnProperty 为 true
---------- ( 重要 )
function Cat(name, color) {
this.name = name;
this.color = color;
this.meow = function () {
console.log('喵喵');
};
}
var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');
cat1.meow === cat2.meow --- 不相等,函数是引用类型的数据,说明无法共享属性
// false
obj.hasOwnProperty(prop) --- 表示:prop是否是obj的自身属性,不含继承属性
cat1.hasOwnProperty('name'); // true
总结:
1. 构造函数内部定义的属性,在生成实例对象的时候,会成为实例对象自身的属性。
2. 构造函数各个实例之间无法共享属性,造成系统资源浪费
3. 解决办法是 原型对象
prototype原型对象
原型对象的所有属性和方法,都能被实例对象所共享。
- 也就是说,如果属性和方法定义在原型对象上,那么所有实例对象就能共享这些属性和方法
构造函数生成的实例之间无法共享构造函数的属性和方法,因为这些属性和方法是生成在实例上的
- 对于构造函数来说,prototype属性会在生成实例对象的时候,成为实例对象的原型对象(那么原型对象的属性和方法就能被实例对象所共享)
- 原型对象的属性,不是实例对象自身的属性。修改原型对象,变动就立刻体现在所有实例对象上
- 当实例对象本身没有某个属性和方法时,会在原型对象上寻找该属性和方法,没找到,在去原型的原型上找。直到Object.prototype上都没找到,就返回undefined
总结:
原型对象的作用就是定义: 所有实例对象所共享的属性和方法
function Animal(name) {
this.name = name;
}
Animal.prototype.color = 'white';
// 构造函数的prototype属性在生成实例对象时,会成为实例的原型对象
// prototype对象上的color属性,会被实例共享
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');
cat1.color // 'white'
cat2.color // 'white'
原型链
所有对象都有自己原型对象
- 所有对象都继承了
Object.prototype对象
的属性和方法,这就是所有对象都具有valueOf
方法 和toString
方法的原因,都继承自Object.prototype对象 -
Object.prototype
的原型是null
, null没有任何属性和方法,也没有自己的原型,原型链到此终结,原型链的尽头是null。 - Object.getPrototypeOf() 返回参数对象的原型对象 --------( 重要 )
- 如果自身和原型上都定义了同名的属性,则优先读取自身属性。'overriding'
Object.getPrototypeOf(Object.prototype)
Object.getPrototypeOf() --- 返回参数对象的原型对象
// null
// 表示Object.prototype对象的原型对象是null
var MyArray = function () {};
MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;
var mine = new MyArray();
mine.push(1, 2, 3);
mine.length // 3
mine instanceof Array // true
解析:
1. mine 是构造函数MyArray 生成实例对象
2. 构造函数的 MyArray.prototype对象成为实例对象的原型对象
3. MyArray.prototype指向了数组实例,那么MyArray的实例就具有 数组的属性和方法
4. 所有mine具有数组的属性和方法
5. instanceof运算符 左边是实例 右边是构造函数 返回布尔值
constructor属性
prototype
对象有一个constructor
属性,默认指向 prototype对象所在的构造函数。---------------------------------------------------- ( 重要 )
- constructor属性定义在prototype对象上,就意味着可以被所有实例所继承
(prototype对象是实例对象的原型对象, 实例对象继承原型对象的属性和方法)
- constructor属性的作用,用来确定实例对象由哪个构造函数生成
- 利用constructor属性可以从一个实例对象新建另一个实例对象
constructor属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改constructor属性,防止引用的时候出错。
( 重要 )
function P() {}
P.prototype.constructor === P // true
解析:
1. P构造函数(首字母大写)的prototype属性是一个对象
2. prototype对象的constructor属性指向prototype对象所在的构造函数
3. P.prototype.constructor === P 布尔值就是true
( 重要 )
function P() {}
var p = new P();
p.constructor === P // true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false 是继承的,不是自身的属性
解析:
1. prototype对象的constructor属性,指向prototype对象所在构造函数
2. constructor是实例对象的原型对象的属性,会被实例所继承
所以
实例p继承了constructor属性,p.constructor = P = P.prototype.constructor
利用constructor属性,可以从一个实例生成另一个实例
因为: constructor属性指向的就是构造函数
function Constr() {}
var x = new Constr();
var y = new x.constructor(); //注意x.constructor === Constr,这里要执行
// x.constructor间接调用构造函数
y instanceof Constr // true
- constructor属性表示原型对象与构造函数之间的关联关系,如果修改了原型对象,一般会同时修改constructor属性,防止引用的时候出错。
( 重要 )
function Person(name) {
this.name = name;
}
Person.prototype.constructor === Person // true
Person.prototype = {
method: function () {}
};
Person.prototype.constructor === Person // false
Person.prototype.constructor === Object // true
解析:
1. Person.prototype 修改成了对象
2. 构造函数Person的原型对象改了,但没改constructor属性,导致这个属性不再指向Person
3. 对象的构造函数是Object构造函数
4. 所以修改原型对象时,一般要同时修改constructor属性的指向
instanceof运算符
instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
- instanceof运算符返回的是boolean值
- 表示 ( 对象 ) 是否是 ( 构造函数 ) 的 ( 实例 )
- instance:是实例的意思
- ( instanceof ) 运算符 ( 左边是实例对象 ),( 右边是构造函数 )。
- 它会检查右边构造函数的原型对象prototype, 是否在左边对象的原型链上。
由于instanceof检查整个原型链,因此同一个实例对象,可能会对多个构造函数都返回true。
- instanceof的原理是检查右边构造函数的prototype属性,是否在左边对象的原型链上。
- 有一种特殊情况,就是左边对象的原型链上,只有null对象。这时,instanceof判断会失真。
- instanceof运算符的一个用处,是判断值的类型。
( 重要 )
v instanceof Vehicle -------------- v是否是Vehicle的实例
// 等同于
Vehicle.prototype.isPrototypeOf(v) ----- Vehicle.prototype是否是v的原型
解析:
1. isPrototypeOf() 方法允许你检查一个对象是否存在于另一个对象的原型链上
2. isPrototypeOf()返回的是布尔值
- instanceof的原理是检查右边构造函数的prototype属性,是否在左边对象的原型链上。有一种特殊情况,就是左边对象的原型链上,只有null对象。这时,instanceof判断会失真。
( 重要 )
var obj = Object.create(null);
typeof obj // "object"
Object.create(null) instanceof Object // false
解析:
1. Object.create()
该方法接受一个对象作为参数,然后以它为原型,返回一个实例对象。
该实例完全继承原型对象的属性。
即:object.create()方法 以参数对象为原型,生成实例对象
2. instanceof的原理是检查右边构造函数的prototype对象是否在左边对象的原型链上
3. Object.create(null) instanceof Object
因为右边 Object.prototype === null,不在左边对象的原型链上
左边对象的原型链在Object.prototype对象上终止
instanceof 用来判断值得类型
但是注意:instanceof 运算符只能用于对象,不适用原始类型的值。
此外,对于 undefined 和 null ,instanceOf 运算符总是返回 false。
var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true
var F = function () {};
var f = new F();
Object.getPrototypeOf(f) === F.prototype // true
undefined instanceof Object // false
null instanceof Object // false
Object.getPrototypeOf()
Object.getPrototypeOf() 返回参数对象的原型,是获取原型对象的标准方法
( 重要 )
var F = function () {};
var f = new F();
Object.getPrototypeOf(f) === F.prototype // true
解析:
1. Object.getPrototypeOf() 返回参数对象的原型
2. f 的原型是 构造函数F的 prototype属性
几种特殊对象的原型
1. 空对象的原型是 Object.prototype
2. Object.prototype 的原型是 null
3. 函数的原型是 Function.prototype
Object.setPrototypeOf() --- 注意 返回的是参数对象
Object.setPrototypeOf方法为参数对象设置原型,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象。
- Object.setPrototypeOf() 返回该参数对象。
- Object.setPrototypeOf() 接受两个参数,第一个是现有对象(
参数对象
),第二个是原型对象 - Object.setPrototypeOf() 为参数对象设置原型
( 重要 )
var a = {};
var b = {x: 1};
Object.setPrototypeOf(a, b); // {}
Object.getPrototypeOf(a) === b // true
a.x // 1
解析:
1. Object.setPrototypeOf(参数对象,原型对象) --- 返回的是参数对象
2. Object.setPrototypeOf()方法的作用是: 把第二个参数设置成第一个参数的原型
- new命令可以使用Object.setPrototypeOf方法模拟。
( 重要 ) --- new命令的原理
var F = function () {
this.foo = 'bar';
};
var f = new F();
// 等同于
var f = Object.setPrototypeOf({}, F.prototype); --- 将空对象的原型设置成 F的prototype属性
F.call(f); --------------------------------------- 将F中的this指向 空对象,并执行构造函数
Object.create() --- 返回参数对象的实例对象
Object.create()接受一个对象作为参数,然后以它为原型,返回一个实例对象。该实例完全继承原型对象的属性。
- Object.create() 以参数对象为原型,返回实例对象。
// 原型对象
var A = {
print: function () {
console.log('hello');
}
};
// 实例对象
var B = Object.create(A);
Object.getPrototypeOf(B) === A // true
B.print() // hello
B.print === A.print // true
- 下面三种方式生成的新对象是等价的。
var obj1 = Object.create({});
var obj2 = Object.create(Object.prototype);
var obj3 = new Object();
- 如果想要生成一个不继承任何属性(比如没有toString和valueOf方法)的对象,可以将Object.create的参数设为null。
var obj = Object.create(null);
obj.valueOf()
// TypeError: Object [object Object] has no method 'valueOf
- 使用Object.create方法的时候,必须提供对象原型,如果参数为空,或者不是对象,都会报错。
Object.create()
// TypeError: Object prototype may only be an Object or null
Object.create(123)
// TypeError: Object prototype may only be an Object or null
- Object.create方法生成的新对象,动态继承了原型。在原型上添加或修改任何方法,会立刻反映在新对象之上。
var obj1 = { p: 1 };
var obj2 = Object.create(obj1);
obj1.p = 2;
obj2.p // 2
Object.create方法生成的对象,继承了它的原型对象的构造函数。
( 重要 )
function A() {}
var a = new A();
var b = Object.create(a);
b.constructor === A // true
b instanceof A // true
解析:
1. Object.create()方法生成的对象,继承了它的原型对象的构造函数
2. var b = Object.create(a); ---- b的原型是a, b继承了a的构造函数
所以 b.constructor === A // true
所以 b instanceof A // true
Object.prototype.isPrototypeOf() - 返回布尔值
isPrototypeOf方法被实例对象所继承
实例对象的 isPrototypeOf() 方法,用来判断该对象是否是参数对象的原型
- 只要实例对象处在参数对象的原型链上, isPrototypeOf 方法就返回true
Object.prototype.__proto__
- 实例对象的
__proto__
返回该实例对象的原型 -
__proto__
该属性可读写 - 只需要浏览器才需要部署该属性,是一个内部属性
- 尽量少用
__proto__
属性,而是使用
Object.getPrototypeOf()
读参数对象的原型
Object.setPrototypeOf()
写第一个参数对象的原型为第二个参数对象
componentDidMount() {
const obj1 = {};
const obj2 = {'name': 'wang'};
obj1.__proto__ = obj2;
console.log(obj2.isPrototypeOf(obj1), '实例对象的isPrototypeOf()放回一个布尔值,表示实例对象是否是参数对象的原型');
console.log(Object.getPrototypeOf(obj1), 'getPrototypeOf()方法,返回参数对象的原型对象');
}
获取原型对象的方法比较
获取实例对象的原型有三种方法: Object.getPrototypeOf()胜出
-
obj.__proto__
实例对象继承的__proto__
属性 -
obj.constructor.prototype
----- 实例的constructor是继承自实例构造函数的 prototype上的constructor Object.getPrototypeOf()
(重要) -- 比较三种获取原型的方法
1. __proto__ 只在浏览器上有 ---- 不靠谱
2. obj.constructor.prototype在手动修改原型时,还要修改构造函数,否则可能会失效
结论:使用Object.getPrototypeOf() 最好
Object.prototype.hasOwnProperty
判断某个属性,是在对象自身属性还是继承属性
- 实例对象的 hasOwnProperty 属性返回一个布尔值,用于判断某个属性定义在对象自身,还是定义在原型链上
- hasOwnProperty是js中唯一一个处理对象属性时,不会遍历原型链的方法
in运算符
in运算符返回一个布尔值,表示一个对象是否具有某个属性
- 注意: ( in ) 运算符 ( 不区分 ) 是 ( 自身属性 ) 还是 ( 继承属性 )
for ...in 循环
自身属性和继承属性都会被for...in循环遍历
- 为了只遍历自身属性,可以在for...in循环内部,用hasOwnProperty来过滤只有是自身属性时,才遍历
const obj1 = {'name': 'wang'};
const obj2 = {'age': 20};
Object.setPrototypeOf(obj1, obj2);
for(let x in obj1) {
console.log(x, 'x')
}
// name x
// age x
for(let y in obj1) {
if( obj1.hasOwnProperty(y) ) { ---- 参数属性是否是实例对象的自身属性
console.log(y, 'y')
}
}
// name y
对象的拷贝
如何要拷贝对象,需要有两点相同
- 拷贝后的对象,要与原对象具有相同的 原型对象
- 考背后的对象,要与元对象具有相同的 实例属性
2018-7-1
语句
js程序的执行单位是 (行),一般情况下,每一行就是一个语句。
- 语句是为了完成某种任务而进行的操作
- 语句以分号结尾,一个分号就表示一个语句的结束
- 多个语句可以写在一行内
- 分号前面可以没有任何内容,js将其视为空语句
var a = 1 + 3 ; var b = 'abc';
解析:
1. 语句:语句以分号结尾;多个语句可以写在一行内。
2. 表达式:为了得到返回值的计算式
3. 凡是预期为值的地方,都可以使用表达式
4. 赋值语句,等号右边预期为值,所以可以使用各种表达式
表达式
表达式: 是一个为了得到返回值的计算式
语句和表达式的区别
-
语句是为了完成某种任务而进行的操作,一般不需要返回值
-
表达式是为了得到返回值,一定会返回一个值
-
js中,凡是预期为值的地方,都可以使用表达式(
重要
)
变量
变量是对值的具名引用
-
变量
是对值的具名引用
- 变量就是为值取名 ,引用这个名字,就是引用这个值 ----------------------------- ( 重要 )
- 变量的名字,就是变量名
- 变量名区分大小写
- 如果只是声明变量,而未赋值,则改变量的值是 undefined
- 可以在同一条var命令中声明多个变量
- JavaScript 是一种动态类型语言,也就是说,变量的类型没有限制,变量可以随时更改类型
函数
js把函数看成一种值,凡是能使用值的地方,就能使用函数
- 当有return语句时,返回return后面紧跟的表达式
- 没有return语句时,返回undefined
- 函数只是一个可执行的值
- (表达式是为了得到返回值的计算式,目的是为了得到返回值,预期为值的地方,都可以使用表达式)
- 函数与其他数据类型地位相等,叫做第一等公民
函数名的提升
js将函数名,视同变量名。
- 在用function命令声明函数时,function后面的变量名存在变量提升
- 注意:采用变量赋值时,如果先调用就会报错
- 因此,如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。
- 不能在条件语句中声明函数 ( if ) 和 ( try )
( 重要 )
总结:
用变量赋值的方法声明函数,和function声明都存在变量提升
但是:变量赋值先调用会报错,而function声明不会报错
( 先调用函数,function声明不报错,变量赋值声明报错 )
1. function命令声明时的 ( 函数名提升 )
f();
function f() {}
不会报错
2. 变量赋值声明函数
f();
var f = function (){};
报错TypeError: undefined is not a function,---------------- 实际上相当于下面的代码:
var f;
f();
f = function () {};
函数的name属性
返回函数的名字
- function声明的函数,返回function后面的函数名
- 变量赋值,匿名函数,返回变量名
- 变量赋值,具名函数,返回function后面的函数名
- name属性的用处,就是获得参数函数的名字
var myFunc = function () {};
function test(f) {
console.log(f.name);
}
test(myFunc) ----------------------- 返回 myFunc
上面代码中,函数test内部通过name属性,就可以知道传入的参数是什么函数。
length属性
函数的length返回函数定义时的参数个数
- 返回 ( 定义时 ) 的 ( 参数个数 )
- length方法提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的
( 方法重载 ) overload
const a = function(a,b,c) {
return `${a},${b},${c}`
};
console.log( a.length ); ------------------------- length返回函数定义时的参数个数: 3
console.log( a(1,2) ); // 1,2,undefined
函数的toString方法
- toString方法返回一个字符串,内容时函数的源码 ( 包括函数内部的注释代码 )
总结: name属性,length属性,toString()方法
const a = function(a,b,c) {
return `${a},${b},${c}`
};
console.log( a.toString(), 'toString方法,返回函数的源码字符串' )
console.log( a.length, 'length属性,返回函数定义时的参数个数' );
console.log( a.name, 'name属性返回函数的名字,function命令声明,时后面的函数名,变量赋值,匿名时变量名,具名时function后的函数名')
console.log( a(1,2) );
结果:
function a(_a, b, c) {
return _a + ',' + b + ',' + c;
} toString方法,返回函数的源码字符串
3 "length属性,返回函数定义时的参数个数"
a name属性返回函数的名字,function命令声明,时后面的函数名,变量赋值,匿名时变量名,具名时function后的函数名
1,2,undefined
函数的作用域
( 作用域 ) scope指的是 ( 变量存在的范围 )
es5有两种作用域:( 全局作用域 ) 和 ( 函数作用域 )
es6新增一个作用域:( 块级作用域 )
- 全局作用域:在整个程序中存在,任何地方都可以读取
- 函数作用域:只能在函数内部读取,函数外部无法读取。( 用回调函数解决 )
- 块级作用域: 只在代码块内有效
- 函数外部声明的变量,是全局变量,可以在函数内部读取 (global variable全局变量)
- 函数内部定义的变量,函数外部无法读取,称为局部变量 (local variable局部便变量)
- 函数内部定义的变量,会在该作用域内,覆盖同名全局变量
注意: 对于var命令,局部变量只能在函数内部声明,在其他区块声明,一律都是全局变量
var a = 1;
var c = 3;
function x() {
console.log(a, '函数内部,可以读取函数外部的全局变量');
var b = 2;
var c = 4;
console.log(c, '函数内部定义的变量,会在该作用域内覆盖同名全局变量')
}
x();
if(true) {
var y = 100;
}
console.log(y, '对于var命令来说,局部变量只能在函数中声明,在其他区块中声明的变量都是全局变量')
console.log(b, '函数外部无法读取函数内部的变量')
函数内部的变量提升
- 和全局作用域一样,函数作用域也会产生变量提升现象
- var声明的变量,不管在什么位置,变量声明都会被提到函数头部
function foo(x) {
if (x > 100) {
var tmp = x - 100;
}
}
-- 等同于
function foo(x) {
var tmp;
if (x > 100) {
tmp = x - 100;
};
}
函数本身的作用域
函数本身也是一个值,也有自己的作用域
- 函数就是一个计算的值,凡是使用值的地方都可以使用函数
- 表达式就是为了得到返回值的计算式,目的是为了得到返回值,凡是预期为值的地方,都可以使用表达式
- 函数的作用域,是其声明时所在的作用域,与其运行时的作用域无关
函数的作用域,是函数声明时的作用域,与函数运行时的作用域无关
- 函数运行时的作用域,是函数声明时的作用域,与函数调用时的作用域无关
-
容易犯错的点: 如果a函数调用b函数,却没有考虑到b函数不会引用a函数内部的变量
--------- ( 重要 )
var a = 1;
function x() {
console.log(a, '函数的作用域,是函数声明时的作用域,与函数调用时的作用域无关');
console.log('容易犯错的点: 如果a函数调用b函数,却没有考虑到b函数不会引用a函数的变量');
}
function y() {
var a = 100;
x();
}
y(); ---------------- 结果是1
总结:
- 函数外部声明的函数,作用域绑定在函数外部
- 函数内部声明的函数,作用域绑定在函数内部
( 重要 )
总结:
1. 函数外部声明的函数,作用域绑定在函数外部
2. 函数内部声明的函数,作用域绑定在函数内部
第一个和第二点都满足第三点
3. 函数的作用域,是函数声明时的作用域,与函数调用时的作用域无关 !!!!!!!!
(1)
var a = 1;
var x = function () {
console.log(a);
};
function y(f) {
var a = 2;
f();
}
y(x) -------- 结果是1 ( 函数的作用域,是函数声明时的作用域,与函数调用时的作用域无关 )
(2)
function foo() {
var x = 1;
function bar() {
console.log(x);
}
return bar;
}
var x = 2;
var f = foo();
f() -------- 结果是1 ( 函数的作用域,是函数声明时的作用域,与函数调用时的作用域无关 )
参数
- 函数没办法省略靠前的参数,而保留靠后的参数
- 如果一点要省略靠前的参数,只有显示的传入 undefined
function f(a, b) {
return a;
}
f( , 1) // SyntaxError: Unexpected token ,(…) 省略前面报错
f(undefined, 1) // undefined 一定要省略前面,则只要传入undefined
参数的传递方式
- 函数的参数如果是原始类型的值(数值,字符串,布尔值),传递方法是( 传值传递 ),
传递的是原始值的拷贝,在函数体内修改参数,不会影响到函数外部
- 函数的参数如果是原始类型的值(数值,字符串,布尔值),传递方法是( 传值传递 ),
- 函数的参数是复合类型的值(数组,对象,其他函数),传递方式是( 传址传递 ),
传递的是原始值的地址指针,在函数内部修改参数,会影响到原始值
- 函数的参数是复合类型的值(数组,对象,其他函数),传递方式是( 传址传递 ),
- 注意:如果函数内部修改的不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值
- reference是引用的意思
- ( 变量 ) 分为 ( 基本类型 ) 和 ( 引用类型 )
let str = 'abcdef';
let num = 12345;
let boo = true; ------------------------------------ 原始类型的值
let arr = [1,2,3,4,5];
let obj = {'name': 'wang'}; ------------------------- 复合类型的值
let replaceArr = [1,2];
let replaceObj = {'name': 'zhang'} ------------------ 复合类型的值
function pass(num,str,boo, arr,obj,fun, replaceArr,replaceObj) {
num = 100;
str = 'oneHundred';
boo = false; ---------- 参数是原始类型的值,传值传递,修改参数,不影响原始值
arr[0] = 100;
obj.name = 'li'; --------- 参数是复合类型的值,传址传递,修改参数,影响原始值
replaceArr = [1,1,1];
replaceObj = {'age': 30} --- 替换掉整个复合类型的参数,不会影响到原始值
}
pass(str, num, boo, arr, obj, replaceArr, replaceObj);
console.log(str, num, boo);
console.log(arr, obj);
console.log(replaceArr, replaceObj);
结果:
abcdef 12345 true
[100, 2, 3, 4, 5] {name: "li"}
[1, 2] {name: "zhang"}
同名参数
如果有同名参数,则取最后出现的那个值
function f(a, a) {
console.log(a);
}
f(1, 2) // 2
-------------------------------------
function f(a, a) {
console.log(a);
}
f(1) // undefined
arguments对象
arguments对象包含了函数 ( 运行时的所有参数 )
- arguments[0] 是第一个参数,arguments[1] 是第二个参数,以此类推...
- arguments对象只能在函数内部使用
正常模式下,arguments对象可以在运行时修改
严格模式下,arguments对象是一个只读对象,修改无效,当不会报错
- arguments对象有一个callees属性,返回它对应的原函数
function a(b,c,d,e) {
console.log(arguments.length, 'arguments.length返回调用时传入的参数个数,是调用时的参数个数');
arguments[0] = 100;
console.log(arguments[0], 'arguments对象,可以在运行时修改');
console.log('在严格模式下,arguments对象是只读,修改无效,当不会报错');
}
a(1,2,3);
console.log(a.length, '函数的length属性,返回函数定义时的参数个数');
console.log( arguments[1] ,'arguments对象只能在函数内部使用')
console.log(arguments.callee, 'arguments对象的callee属性,返回它对应的原函数');
结果:
3 "arguments.length返回调用时传入的参数个数,是调用时的参数个数"
100 "arguments对象,可以在运行时修改"
在严格模式下,arguments对象是只读,修改无效,当不会报错
4 "函数的length属性,返回函数定义时的参数个数"
undefined "arguments对象只能在函数内部使用"
报错: callee不能用于严格模式,react的类默认就是严格模式
注意:
1. 函数的length属性,返回函数定义时的参数个数
2. arguments.length属性,返回函数调用时传入的参数个数
3. arguments对象只能在函数内部使用
闭包
闭包是定义在函数内部的函数
- 函数的作用域是函数定义时的作用域,与调用时的作用域无关
- 闭包的用处:
- 读取函数内部的变量
- 将这些变量保存在内存中,即闭包可以记住它的诞生环境一直存在
- 封装对象的私有属性和私有方法
( 重要 )
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var n = 111;
var result = f1();
result(); // 999
解析:
1. 函数的作用域,是函数定义时的作用域,与函数调用时的作用域无关
2. f2声明在f1的内部,f2调用时,f2的作用域是定义时候的作用域,即在f1的内部,所以是999,不是111
3. 闭包就是定义在一个函数内部的函数,注意: 一定是要在函数内部定义 !!!
( 闭包 )
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
解析:
1. 闭包inc使得函数createIncrementor的内部环境,一直存在。
2. inc始终在内存中,而inc的存在依赖于createIncrementor,因此也始终在内存中
不会在调用结束后,被垃圾回收机制回收。
- 闭包用于封装对象的私有属性和私有方法
闭包:用与封装对象的私有属性和私有方法
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name,
getAge: getAge,
setAge: setAge
};
}
var p1 = Person('张三');外层函数每次运行,都会生成一个新的闭包,闭包保存外层函数的内部变量,内存消耗大
p1.setAge(25);
p1.getAge() // 25
注意:
1. 外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。
2. 因此不能滥用闭包,否则会造成网页的性能问题。
立即调用函数表达式
有时候,我们需要在定义函数之后,立即调用该函数。这时,你不能在函数的定义之后加上圆括号,这会产生语法错误。
- 产生错误的原因是function关键字,可以作为语句,也可以作为表达式
-
语句
:用分号结尾,多个语句可以在一行。 -
表达式
:为了得到返回值的计算式,凡是预期为值的地方都可以使用表达式; -
js规定,function关键字在行首,一律解释成语句。
因此,JavaScript引擎看到行首是function关键字之后,认为这一段都是函数的定义,不应该以圆括号结尾,所以就报错了。 - 解决方法就是不要让function出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。
- 总结:
- function出现在行首,是语句。语句不能以括号
()
结尾,必须以分号;
结尾
- function出现在行首,是语句。语句不能以括号
- 用变量赋值方法声明的函数,function...是一个表达式,直接加
()
就能立即执行调用
- 用变量赋值方法声明的函数,function...是一个表达式,直接加
- 如果function在行首,又要立即调用,包一层括号即可
( 重要 )
语句
function f() {} ------- 函数的声明语句
表达式
var f = function f() {} -------- 等号右边预期是值,function是表达式,函数就是一个可执行的值
(function(){ ... }()); ------------- 语句不能以括号结尾,而是以分号结尾
// 或者
(function(){ ... })();
注意,上面两种写法最后的分号都是必须的。
如果省略分号,遇到连着两个 IIFE,可能就会报错。
--------------------------
注意: 如果是采用变量赋值的方式声明函数,那么function本来就是表达式,所以可以直接加括号
const a = function() {
console.log('11111111')
}();
eval命令
eval命令的作用是,将字符串当作语句执行
- eval命令的作用是将字符串,当作语句执行
eval('var a = 1;');
a // 1