美文网首页
04ES5--基础语法(二)

04ES5--基础语法(二)

作者: 修_远 | 来源:发表于2020-11-03 23:44 被阅读0次

第一篇讲了基本语法以及基本数据类型,第二篇开始讲解复杂数据类型——对象、函数、数组。

对象

定义

这个是要跟之前移动端的概念要区分的地方,移动端的对象值的是一个类alloc(new)出来的一个实体,譬如说一个Person类new一个对象 p,那么p就是一个对象。一个NSDictionary类,可以创建一个字典实体。

但是,在JS中,对象指的是一组“键值对”的集合,是一种无序的复合数据集合。这种数据结果其实对应iOS中的字典、JAVA中的map,所以在js中,说到对象时,一定一定不要联想到Person类(本质是结构体),其实就是一个键值对、一个字典、一个map而已。

键名

  • 键名都是字符串(ES6 又引入了 Symbol 值也可以作为键名)
  • 如果键名不是字符串,则会自动转为字符串,如果转换失败,是会报错的。譬如说:1ph wp+q这类非常规变量
  • 对象中的键名也称为属性,属性的引用通过点语法,如果属性值为对象,则构成了链式调用
  • 键值可以是任意数据类型。如果一个属性的值为函数,通常称之为“方法”,可以像函数那样调用

对象的引用

多个变量指向同一个对象时

  • 修改其中一个变量,会影响到其他所有变量
  • 移除某一个变量,对原对象以及其他变量都不会造成影响

函数

{ foo: 123 }

为了避免这种歧义,JavaScript 引擎的做法是,如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块。

如果要解释为对象,最好在大括号前加上圆括号。因为圆括号的里面,只能是表达式,所以确保大括号只能解释为对象。

({ foo: 123 }) // 正确
({ console.log(123) }) // 报错

例如 eval 语句:对字符串求值

  • 如果没有圆括号,则理解为一个代码块
  • 如果有圆括号,则理解成一个对象
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}

属性的读取

  • 两种方式:点运算符方括号运算符
  • 点运算符后面不能跟数字,因为会被当做小数点处理。例如:obj.123 这种情况下只能用方括号 obj[123]
  • 方括号运算符的键名必须放在引号里面,否则会当做变量处理(如果是数字类型,可以不用引号)

属性的赋值

  • 同样有两种方式:点运算符方括号运算符
  • js中允许 后绑定,也就是说,你可以在任意时刻新增属性,没必要在定义对象的时候,就定义好属性。

属性的查看: Object.keys

var obj = {
  key1: 1,
  key2: 2
};

Object.keys(obj);
// ['key1', 'key2']

属性的删除:delete

  • delete命令用于删除对象的属性,删除成功后返回true
  • 删除一个不存在的属性,delete不报错,而且返回true
  • delete命令只能删除对象本身的属性,无法删除继承的属性(无法删除父类的属性)
var obj = { p: 1 };
Object.keys(obj) // ["p"]

delete obj.p // true
obj.p // undefined
Object.keys(obj) // []
  • 只有通过 Object.defineProperty 声明的属性,在删除的时候返回 false,表示该属性是不能删除的
var obj = Object.defineProperty({}, 'p', {
  value: 123,
  configurable: false
});

obj.p // 123
delete obj.p // false

属性的遍历:for...in

  • 它遍历的是对象所有可遍(enumerable)的属性,会跳过不可遍历的属性
  • 它不仅遍历对象自身的属性,还遍历继承的属性
var obj = {a: 1, b: 2, c: 3};

for (var i in obj) {
  console.log('键名:', i);
  console.log('键值:', obj[i]);
}
// 键名: a
// 键值: 1
// 键名: b
// 键值: 2
// 键名: c
// 键值: 3
  • 结合 hasOwnProperty,在循环内部判断是否是自身的属性
var person = { name: '老张' };

for (var key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key);
  }
}
// name

with 语句

  • 操作同一个对象的多个属性时,提供书写的方便
var obj = {
  p1: 1,
  p2: 2,
};
with (obj) {
  p1 = 4;
  p2 = 5;
}
// 等同于
obj.p1 = 4;
obj.p2 = 5;
  • with区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创建一个当前作用域的全局变量
var obj = {};
with (obj) {
  p1 = 4;
  p2 = 5;
}

obj.p1 // undefined
p1 // 4
  • 【总结】尽量不要使用 with 操作,他会将变量的决议放到运行时判断,这样会拖慢运行速度。下面是一个使用临时变量替换 with 的案例:
with(obj1.obj2.obj3) {
  console.log(p1 + p2);
}

// 可以写成
var temp = obj1.obj2.obj3;
console.log(temp.p1 + temp.p2);

函数

函数是一段可以反复代用的代码块。函数还能接收输入的参数,不同的参数会返回不同的值。

函数的声明

1、function 命令

格式:function 函数名(参数列表) {...}

function print(s) {
  console.log(s);
}

在其他地方调用 print() 就可以调用上述代码了

2、函数表达式

var print = function(s) {
  console.log(s);
};

print 表示是左边匿名函数的一个变量,可以直接调用 print()

也可以写成第一种样式,带函数名的格式,但是这个函数名只能在函数体里面使用

var print = function x(){
  console.log(typeof x);
};

x
// ReferenceError: x is not defined

print()
// function
  • = 左边的 print 课可以表示是这个函数的一个函数指针
  • = 右边的 x 函数名,只能在这个函数体里面使用,如果在其他地方使用,则会报错。

3、构造函数

var add = new Function(
  'x',
  'y',
  'return x + y'
);

// 等同于
function add(x, y) {
  return x + y;
}
  • 可以传递任意多个参数给 Funtion 构造函数,最后一个参数会被当做函数体
  • 如果只有一个参数,该参数就是函数体
  • 这种写法不直观,几乎无人使用

4、函数的重复声明

  • 后声明的含食宿会覆盖前面声明的函数
  • 如果两个同名函数,分别是使用函数表达式声明的、和使用 function 命令声明的,那么使用函数表达式声明的会覆盖使用 function 命令声明的函数,不区分先后

5、第一等公民

  • 因为函数与其他数据类型地位平等,所以在 js 中被称为第一等公民
  • 函数只是一个可以执行的值,对比其他数据类型,并无特殊之处

6、函数名的提升

  • 使用 function 声明函数时,整个函数就会想变量一样,被提升到代码的头部
  • 但是使用函数表达式声明的函数,是不会被提升,会报错的

函数的属性和方法

1、name属性:返回函数的名字

  • function 声明的函数,直接返回 function 后面的函数名;
  • 函数表达式声明的函数,如果 = 右边没有函数名,则返回 = 左边的变量名。如果 = 右边有函数名,则返回 = 右边的函数名
  • 主要作用是当函数作为参数时,知道传入的是什么函数

2、length属性:返回函数预期传入的参数个数,参数列表中的参数个数

  • 只返回定义时的参数个数,不管在调用时传入多少个参数,这个值始终不变
  • 作用是可以判断定义时和调用时参数的差异,来实现面向对象编程的 方法重载(overload)

3、toString()方法:以字符串的形式返回函数的源码

  • 只返回自定义函数的源码
  • 对于系统函数,返回固定格式:function (){[native code]}
  • 函数内的注释也会返回,可以根据这个特性实现多行字符串

函数作用域

ES5中作用域分两种:全局作用域和函数作用域

  • 全局作用域:整个程序中一直存在,所有地方都可读取

  • 函数作用域:变量只在函数内部存在

1、函数内部的变量提升

看到这里,变量提升这个概念已经出现过非常多次了,在讲ES6中let的篇幅中,就着重讲过变量提升,前面的基础语法(一)中也讲到了变量提升,这个其实是允许我们在声明函数、变量前使用它们。原因就是他们的声明其本质是被提前了的。

  • 在函数内部,var 声明的变量,不管在什么位置,变量声明都会被提升到函数的头部
function foo(x) {
  if (x > 100) {
    var tmp = x - 100;
  }
}

// 等同于
function foo(x) {
  var tmp;
  if (x > 100) {
    tmp = x - 100;
  };
}

2、函数本身的作用域

这里其实只要考虑两点就可以理解得非常清楚了

  1. 闭包值捕获
var a = 1;
var x = function () {
  console.log(a);
};

function f() {
  var a = 2;
  x();
}

f() // 1

因为x函数在声明的时候,a=1,这个值是捕获的全局变量a的值。而在函数f中,这个a=2,表示的是局部变量a的值,所以x输出的值还是捕获的全局变量a的值

  1. 函数的嵌套

嵌套的调用:y中的a只在y中才声明,在x作用域中是没有这个变量的,所以找不到

var x = function () {
  console.log(a);
};

function y(f) {
  var a = 2;
  f();
}

y(x)
// ReferenceError: a is not defined

嵌套的声明:bar作为foo作用域内部的作用域,所以能访问到x

function foo() {
  var x = 1;
  function bar() {
    console.log(x);
  }
  return bar;
}

var x = 2;
var f = foo();
f() // 1

函数的参数

  1. 函数参数是不必要的,js允许省略参数;
  2. 调用函数参数时传的参数个数,可以跟函数声明的参数个数完全不同;
  3. 调用函数参数时,按从左到右的顺序入参,不能直接省略左边的参数。如果要省略,通过传入 undefined,例如:f(undefined, 1)
  4. 如果参数是原始类型的值,则是通过值拷贝的方式传递,无论函数内容如何修改,都不会影响到外部的值;
  5. 如果参数是符合类型的值,则是通过地址传递的方式传递,也就是在函数内部修改参数,将会影响到外面的值;(如果是直接修改参数的引用,是不会影响到外面的值。可以将函数内部的参数看做是对象的另一个变量);
  6. 如果函数参数列表中出现同名的参数,则取最右边的参数;

arguments 对象

  1. arguments 对象可以读取函数在运行时的所有参数,arguments[0]arguments[1]arguments[2]……
  2. arguments.length 可以读取函数在运行时的参数个数;
  3. arguments 可以在运行时动态修改传入的参数,例如:arguments[0]=99,如果第一个参数是0,最后也会使用99;
  4. 使用 'use strict' 可以禁止 arguments[0] 对参数的修改,在函数头部声明下 'use strict' 就可以了;
  5. arguments 本质是一个对象,不能使用数组的 slice、forEach 等方法;
  6. arguments.callee 返回它所对应的原函数,可以达到调用自己的作用,但是在严格模式('use strict')下是禁止的,所以不建议使用;

闭包

1、闭包的定义:定义在一个函数内部的函数

2、 闭包的特性:能记住(捕获)它被定义的环境,也就是闭包可以记住它所在的那个函数的环境

3、闭包的作用:

  1. 可以读取函数内部的变量;
  2. 让这些变量一直保存在内存中,即闭包可以使它诞生的环境一直存在;
  3. 封装对象的私有属性和私有方法;
function createIncrementor(start) {
  return function () {
    return start++;
  };
}

var inc = createIncrementor(5);

inc() // 5
inc() // 6
inc() // 7

上面例子中:
函数定义时的持有关系是

createIncrementor 
--> function 
----> start

执行代码:var inc = createIncrementor(5); 之后变成了

createIncrementor 
--> function 
----> start

inc 
--> function 
----> start

inc 将 function 函数持有,导致 function 函数无法释放。因为 createIncrementor 持有的 function 没有被释放,所以 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

【注意】,外层函数每次运行,都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大。因此不能滥用闭包,否则会造成网页的性能问题。

立即调用的函数表达式(IIFE)

1、立即调用的函数表达式的作用

  1. 不必为函数命名,避免污染全局变量;
  2. IIFE内部形成一个单独的作用域,可以封装一些外部无法读取的私有变量;

2、IIFE的写法

  1. 函数实现后面接 ()
  2. 结尾要写封号 ;
  3. 避免function出现在首行,需要使用圆括号表达式,将其理解为一个表达式,而不是一个关键字;

不要返回值的格式

(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();

使用返回值的格式

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

eval 命令

eval 命令接收一个字符串作为参数,并将这个字符串当做语句执行

  1. 不要使用 eval 命令;
  2. eval 命令存在很严重的安全问题,可能会修改外部命令;
  3. 引擎只能识别 eval 的直接调用,如果是通过 eval 别名调用的,引擎一律无法识别;
  4. eval 的作用域就是当前它所在的作用域;
  5. eval 中的语句如果无法执行,则会报错;
  6. eval 中的语句需要有实际意义,否则有可能报错。例如:eval('return ;)`;

数组

  1. 数组的本质是对象(object);
  2. 数组内可以存放任何类型的数据;
  3. 数组的 length 属性是可读可写,可以通过将 length 设为0来清空数组。如果设置其他类型、或者是边界值之外的值,会报错;
  4. in 运算符:判断某个键名是否存在。适用于对象、也适用于数组;
  5. 数组的遍历,建议通过 forwhileforEach 来遍历,不是使用对象的遍历 for...in,是因为它会输出数组作为对象的一些属性,超出长度之外的内容也会被输出;
  6. delete 运算符删除一个数组成员后,会形成空位,并不会影响数组的 length 属性;
  7. 空位的几种样式,例如:var a = [1, , 1];var a = [, , ,];,末尾的逗号不会形成空位;
  8. 空位在被遍历时会被跳过,而 undefined 在遍历时不会被跳过;
  9. 很多类似数组的对象,有 length 属性,可以通过下标取值,例如:arguments对象
var obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};

obj[0] // 'a'
obj[1] // 'b'
obj.length // 3
obj.push('d') // TypeError: obj.push is not a function

相关文章

  • 04ES5--基础语法(二)

    第一篇讲了基本语法以及基本数据类型,第二篇开始讲解复杂数据类型——对象、函数、数组。 对象 定义 这个是要跟之前移...

  • 04ES5--基础语法(一)

    基础语法 基本概念:变量、标识符、注释、区块 条件语句:if-else、switch、三元运算符 循环语句:whi...

  • Scala函数式编程(三) scala集合和函数

    前情提要: scala函数式编程(二) scala基础语法介绍 scala函数式编程(二) scala基础语法介绍...

  • 022 JS操作

    JS基础操作 一、分支结构 1、if语句 if 基础语法 if 复杂语法 if 嵌套 2、switch语句 二、循...

  • 技术栈

    一、HTML、CSS基础、JavaScript语法基础。 二、JavaScript语法进阶。包括:作用域和闭包、t...

  • 2020前端技术栈

    一、HTML、CSS基础、JavaScript语法基础。二、JavaScript语法进阶。包括:作用域和闭包、th...

  • 二.基础语法

    2.1不一样的类型声明 2.2val和var的使用规则 2.3高阶函数和Lambda 2.4面向函数式编程 2.5...

  • 二.基础语法

    1.编码 python3 默认使用UTF-8进行编码,也可以自己进行指定 # -*- "coding":"cp-1...

  • 二、基础语法

    select 与 select distinct where

  • TS基础篇10:模块

    第一:基础语法 第二:包别名

网友评论

      本文标题:04ES5--基础语法(二)

      本文链接:https://www.haomeiwen.com/subject/ytgvvktx.html