深入浅出原生JS之基本函数

Arguments对象:

在函数代码中,使用特殊对象arguments,开发者无需明确指出参数名,就能访问它们。

可用于模拟函数的重载:

function doAdd(){
    if(arguments。length==1){
        //但只有一个参数的时候,加5
        alert(arguments[0] +5);
        }else if(arguments。length ==2){
            //但有两个参数的时候,这两个参数相加
            alert(arguments[0]+argument[1]);
    }
}
doAdd(10);//输出15
doAdd(40,20);//输出60

虽然不如重载好,不过已足以避开ECMAScript的这种限制

eval函数

函数可计算某个字符串,并执行其中的JavaScript代码;表示JavaScript表达式,语句或一系列语句的字符串。表达式可以包含变量以及已存在对象的属性。

在非严格的js模式下:

var x=2;
console.log(eval(“var x=5;x”));//5
console.log(x);//5

在严格的js模式下:

"user strict";
var x =2;
console.log(eval("var x =5;x"));//5
console.log(x);//2

正常模式下,eval语句的作用域,取决于它出于全局作用域,还是处于函数作用域.严格模式下,eval语句本身就是一个作用域,不再能够生成全局变量了,它所生成的变量只能用于eval内部.

eval()通常比替代方法慢,因为它必须调用js解释器,而许多其他结构则由现代就是引擎进行优化.

箭头函数和普通函数的区别:

  • 箭头函数属于匿名函数,匿名函数是要通过赋值语句赋值给变量,这个赋值过程是在代码执行阶段执行的,不是在声明阶段,所以没有函数声明的提升属性.

  • 箭头函数中的this和调用时的上下文无关,而是取决于定义时的上下文:

      function make(){
          return ()=>{
              console.log(this);
          }
      }
    
      var testFunc =make.call({name:'foo'});
      testFunc();//{name:'foo'}
      testFunc.call({name:'bar'});//name:'foo'}
      //如果是普通函数的话,结果就是{name:'bar'}
    

上述例子中可看出,确实箭头函数在定义之后,this就不会发生改变了,无论用什么样的方式调用它,this都不会改变:但严格来说,这并不是"取决于定义时的上下文",因为箭头函数根本就没有绑定自己的this,在箭头函数中调用this时,仅仅是简单的沿用作用域链向上寻找,找到最近的一个this拿来使用罢了;在普通函数中,会自动绑定上的各种局部变量,箭头函数都是十分单纯的沿着作用域向上寻找.

JS中的冒泡流和捕获流

addEventListener第三个参数useCapture,true时为捕获,false为冒泡

冒泡从目标对象开始,向父级元素至window传递;捕获从window底层逐级至目标对象传递!

事件捕获

当你使用事件捕获时,父级元素先触发,子级元素后触发,即div先触发,p后触发.

事件冒泡

当你使用事件冒泡时,子级元素先触发,父级元素后触发,即p先触发,div后触发.
image

stopImmediatePropagation和stopPropagation()

后者只会阻止冒泡或者是捕获.但是前者除此之外还会阻止该元素的其他事件发生,但是后者就不会阻止其他事件的发生.

变量作用域

  • 在JS中,用var申明的变量实际上是有作用域的.

    如果一个变量在函数内部声明,则该变量的作用域为整个函数体,在函数体外不可被引用该变量:

      'use strict'//严格模式
      function foo(){
          var x=1;
          x=x+1;
      }
      x=x+2;//ReferenceError!无法在函数体外引用变量x
    
  • 如果两个不同函数各自声明了同一个变量,那么该变量只在各自的函数体内起作用,换句话说,不同函数内部的同名变量互相独立,互不影响:

      'use strict';
      function foo(){
          var x=1;
          x=x+1;
      }
      
      function bar(){
          var x='A';
          x=x+'B';
      }
    
  • 由于JS的函数可以嵌套,此时,内部函数可以访问外部函数定义的变量,反过来则不行:

      'use strict';
      function foo(){
          var x=1;
          function(){
              var y=x+1;//bar可以访问foo的变量x!
          }
          var z=y+1;//ReferenceError!foo不可以访问bar的变量y!
      }
    
  • 如果内部函数和外部函数的变量名重名,怎么办?

JS的函数在查找变量时从自身函数定义开始,从’内’向’外’查找.如果内部函数定义了与外部函数重名的变量,则内部函数的变量将"屏蔽"外部函数的变量.

全局作用域

不在任何函数内定义的变量就具有全局作用域.实际上,JS默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:

'use strict';
var course = 'Learn JS';
alert(course);//Learn JS
alert(window.course);//Learn JS

直接访问全局变量course和访问window.course是完全一样的.

由于函数定义有两种方式,以变量方式var foo=function (){}定义的函数实际上也是一个全局变量,因此,顶层函数的定义也被视为一个全局变量,被绑定到window对象:

JS实际上只有一个全局作用域.任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,如果最后在全局作用域中也没有找到,则报ReferenceError错误.

名字空间

全局变量会绑定到window上,不同的JS文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现.减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中.:

//唯一的全局变量MYAPP:
var MYAPP={};
//其他变量:
MYAPP.name='myapp';
MYAPP.version=1.0;
//其他函数:
MYAPP.foo=function(){
    return 'foo';
};

把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能.

局部作用域

由于JS的变量作用域实际上是函数内部,我们在for循环等语句块中是无法定义具有局部作用域的变量的;

'use strict';
function foo(){
    for(var i=0;i<100;i++){
    }
    //SyntaxError:
    i+=100;//仍然可以引用变量i
}

为了解决块级作用域,ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量:

'use strict';
function foo(){
    var sum=0;
    for(var i=0;i<100;i++){
        sum+=i;
    }
    //SyntaxError:
    i+=1;
}

常量

由于var和let声明的是变量,如果要声明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示"这是一个常量,不要修改它的值":

var PI=3.14;

ES6标准引入了新的关键字const来定义常量,const域let都具有块级作用域:

'use strict';
const PI=3.14;
PI=3;//某些浏览器不报错,但是无效果!
PI;//3.14

解构赋值

从ES6开始,JS引入了解构赋值,可以同时对一组变量进行赋值.

  • 什么是解构赋值:

解构赋值语法是一种JS表达式,它使得将值从数组,或属性从对象,提取到不同的变量中,成为可能.

解构数组

传统做法把一个数组的元素分别赋值给几个变量:

var array = ['hello','JS','ES6'];
var x=array[0];
var y=array[0];
var z=array[0];

在ES6中,可以使用解构赋值,直接对多个变量同时赋值:

'use strict';
//如果浏览器支持解构赋值就不会报错:
var [x,y,z]=['hello','JS','ES6'];
//x,y,z分别被赋值为数组对应元素:
console.log('x='+x+',y='+y+',z='+z);

注意,对数组元素进行解构赋值时,多个变量要用[…]括起来.

如果数组本身还有嵌套,也可以通过下面的形式进行解构赋值,注意嵌套层次和位置要保持一致:

let[x,[y,z]] = ['hello',['JS','ES6']];
x;//'hello'
y;//'JS'
z;//'ES6'

解构赋值还可以忽略某些元素:

let [,,z]=['hello','JS','ES6'];//忽略前两个元素,只对z赋值第三个元素
z;//'ES6'

解构对象

如果需要从一个对象中取出若干属性,也可以使用解构赋值,便于快速获取对象的指定属性:

'use strict';

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};
var {name, age, passport} = person;

对一个对象进行解构赋值时,同样可以直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的:

var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school',
address: {
    city: 'Beijing',
    street: 'No.1 Road',
    zipcode: '100001'
}
};
var {name, address: {city, zip}} = person;
name; // '小明'
city; // 'Beijing'
zip; // undefined, 因为属性名是zipcode而不是zip
// 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性:
address; // Uncaught ReferenceError: address is not defined

使用解构赋值对对象属性进行赋值时,如果对应的属性不存在,变量将被赋值为undefined,这和引用一个不存在的属性获得undefined是一致的,如果要使用的变量名和属性名不一致,可以用下面的语法获取:

var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};

// 把passport属性赋值给变量id:
let {name, passport:id} = person;
name; // '小明'
id; // 'G-12345678'
// 注意: passport不是变量,而是为了让变量id获得passport属性:
passport; // Uncaught ReferenceError: passport is not defined

解构赋值还可以使用默认值,这样就避免了不存在的属性返回undefined的问题:

var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678'
};

// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
name; // '小明'
single; // true

有些时候,如果变量已经被声明了,再次赋值时,正确的写法也会报语法错误:

// 声明变量:
var x, y;
// 解构赋值:
{x, y} = { name: '小明', x: 100, y: 200};
// 语法错误: Uncaught SyntaxError: Unexpected token =

这是因为JS引擎把{开头的语句当作了块处理,于是=不再合法.解决方法是用小括号括起来:

({x, y} = { name: '小明', x: 100, y: 200});

解构赋值使用场景

解构赋值在很多时候可以大大简化代码.例如,交换两个变量x和y的值,可以这样写,不再需要临时变量:

var x=1, y=2;
[x, y] = [y, x]

快速获取当前页面的域名和路径:

var {hostname:domain, pathname:path} = location;

如果一个函数接收一个对象作为参数,那么,可以直接把对象的属性绑定到变量中.例如,下面的函数可以快速创建一个date对象:

function buildDate({year, month, day, hour=0, minute=0, second=0}) {
return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}

它的方便之处在于传入的对象只需要year,month和day这三个属性:

buildDate({ year: 2017, month: 1, day: 1 });
// Sun Jan 01 2017 00:00:00 GMT+0800 (CST)

也可以传入hour.minute.second属性:

buildDate({ year: 2017, month: 1, day: 1, hour: 20, minute: 15 });
// Sun Jan 01 2017 20:15:00 GMT+0800 (CST)

优点

使用解构赋值可以减少代码量,但是,需要在支持ES6解构赋值特性的现代浏览器中才能运行.目前支持解构赋值的浏览器包括Chrome,Firefox,Edge等.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值