面向对象编程 和 函数 - 复习

2018-6-28复习
每一次回头看基础,都有新的理解

Function.prototype.call

函数实例的call方法,可以指定函数内部this的指向(即:函数执行时所在的作用域,--- 函数运行时所在的对象 ),然后在所指定的作用域中,调用该函数。
注意: call方法是函数的方法
应用:

  1. 将 ( 类似数组的对象 ) 转换成 ( 数组 )

(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方法后,就可以变成,接受一个数组为参数

应用:

  1. 找出数组中最大的数---(或者最小的数)
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])
  1. 将数组的 ( 空元素 ) 变成 ( undefined )
  • 通过apply方法,利用Array构造函数将数组的空元素变成undefined。
  • 空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined。因此,遍历内部元素的时候,会得到不同的结果。
Array.apply(null, ['a', ,'b'])
等于: [...['a', , 'b']]


// [ 'a', undefined, 'b' ]
  1. 转换类似数组的对象
  • 现在有了展开运算符,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()胜出

  1. obj.__proto__实例对象继承的 __proto__属性
  2. obj.constructor.prototype
    ----- 实例的constructor是继承自实例构造函数的 prototype上的constructor
  3. 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

对象的拷贝

如何要拷贝对象,需要有两点相同

  1. 拷贝后的对象,要与原对象具有相同的 原型对象
  2. 考背后的对象,要与元对象具有相同的 实例属性



















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. 函数内部声明的函数,作用域绑定在函数内部
( 重要 )



总结:
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

参数的传递方式

    1. 函数的参数如果是原始类型的值(数值,字符串,布尔值),传递方法是( 传值传递 ),
      传递的是原始值的拷贝,在函数体内修改参数,不会影响到函数外部
    1. 函数的参数是复合类型的值(数组,对象,其他函数),传递方式是( 传址传递 ),
      传递的是原始值的地址指针,在函数内部修改参数,会影响到原始值
    1. 注意:如果函数内部修改的不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值
  • 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对象只能在函数内部使用

闭包

闭包是定义在函数内部的函数

  • 函数的作用域是函数定义时的作用域,与调用时的作用域无关
  • 闭包的用处:
    1. 读取函数内部的变量
    1. 将这些变量保存在内存中,即闭包可以记住它的诞生环境一直存在
    1. 封装对象的私有属性和私有方法
( 重要 )


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出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是将其放在一个圆括号里面。
  • 总结:
    1. function出现在行首,是语句。语句不能以括号()结尾,必须以分号;结尾
    1. 用变量赋值方法声明的函数,function...是一个表达式,直接加()就能立即执行调用
    1. 如果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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容

  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,551评论 0 5
  •   面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意...
    霜天晓阅读 2,100评论 0 6
  • 第3章 基本概念 3.1 语法 3.2 关键字和保留字 3.3 变量 3.4 数据类型 5种简单数据类型:Unde...
    RickCole阅读 5,113评论 0 21
  • 文图/ 资源整合(十六岁那年的诗) 既然钟情于玫瑰 就勇敢地拥抱它 即使弄得 满身是刺 也不必后悔 因为 你毕...
    培红说情感阅读 155评论 0 0
  • 忽然间好想你,人间天堂——美丽杭城;忽然间好想你,浓妆淡抹总相宜的西子湖:忽然间好想你,那些曾经在杭城奋斗打拼的日...
    秦悠然阅读 258评论 4 1