[JavaScript]一:概念/特色/语法/函数/数组

---- 本系列仅为个人学习总结,基于阮一峰大佬的《>>JavaScript 教程<<》---

目录

 一.概念

一些特点总结

二.使用范围 

(1)浏览器的平台化

(2)Node

(3)数据库操作

(4)移动平台开发

(5)内嵌脚本语言

(6)跨平台的桌面应用程序

著名程序员 Jeff Atwood 甚至提出了一条 “Atwood 定律”:

三.>>JavaScript 与 Java 的关系<< 

四.基本语法

1.变量

JavaScript 保留关键字 :

 2.注释

3.区块

4.if()   if...else语句  switch语句 while语句 for语句(与C语言语法基本相同)

等值比较优先使用===,!==   而非 ==   !=  

三元运算符

标签(相当于定位符)

五.数据类型

undefined典型场景

布尔值

对象

链式引用

对象的引用

with()

函数

函数声明

>>函数名的提升<<

name属性

length 属性(定义时的参数个数)

函数本身的作用域

易错点:

闭包

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

作用:

好处:

 数组

>>类似数组的对象<<



 一.概念

JavaScript 是一种轻量级的脚本语言。所谓“脚本语言”(script language),指的是它不具备开发操作系统的能力,而是只用来编写控制其他大型应用程序(比如浏览器)的“脚本”。

JavaScript 也是一种嵌入式(embedded)语言。它本身提供的核心语法不算很多,只能用来做一些数学和逻辑运算。JavaScript 本身不提供任何与 I/O(输入/输出)相关的 API,都要靠宿主环境(host)提供,所以 JavaScript 只合适嵌入更大型的应用程序环境,去调用宿主环境提供的底层 API。

以浏览器为例,它提供的额外 API 可以分成三大类。

  • 浏览器控制类:操作浏览器
  • DOM 类:操作网页的各种元素
  • Web 类:实现互联网的各种功能

如果宿主环境是服务器,则会提供各种操作系统的 API,比如文件操作 API、网络通信 API等等。这些你都可以在 Node 环境中找到。

一些特点总结

  •  JavaScript 语言本身,虽然是一种解释型语言,但是在现代浏览器中,JavaScript 都是编译后运行。
  • 既支持类似 C 语言清晰的过程式编程,也支持灵活的函数式编程,可以用来写并发处理(concurrent)。这些语法特性已经被证明非常强大,可以用于许多场合,尤其适用异步编程。JavaScript 的编程风格是函数式编程和面向对象编程的一种混合体
  • JavaScript 的所有值都是对象,这为程序员提供了灵活性和便利性。因为你可以很方便地、按照需要随时创造数据结构,不用进行麻烦的预定义。
  • 可以采用事件驱动(event-driven)和非阻塞式(non-blocking)设计,在服务器端适合高并发环境,普通的硬件就可以承受很大的访问量。
  • 开放性,它的标准 ECMA-262 是 ISO 国际标准,写得非常详尽明确;该标准的主要实现(比如 V8 和 SpiderMonkey 引擎)都是开放的,而且质量很高。这保证了这门语言不属于任何公司或个人,不存在版权和专利的问题。
  • 原始设计目标是一种小型的、简单的动态语言,与 Java 有足够的相似性,使得使用者(尤其是 Java 程序员)可以快速上手。

二.使用范围 

(1)浏览器的平台化

随着 HTML5 的出现,浏览器不再仅仅能浏览网页,而是越来越像一个平台,JavaScript 因此得以调用许多系统功能,比如操作本地文件、操作图片、调用摄像头和麦克风等等。

(2)Node

Node 项目使得 JavaScript 可以用于开发服务器端的大型项目,网站的前后端都用 JavaScript 开发已经成为了现实,有些嵌入式平台(Raspberry Pi)能够安装 Node

(3)数据库操作

NoSQL 数据库这个概念,本身就是在 JSON(JavaScript Object Notation)格式的基础上诞生的,大部分 NoSQL 数据库允许 JavaScript 直接操作。

(4)移动平台开发

一般来说,安卓平台使用 Java 语言开发,iOS 平台使用 Objective-C 或 Swift 语言开发。许多人正在努力,让 JavaScript 成为各个平台的通用开发语言。

(5)内嵌脚本语言

越来越多的应用程序,将 JavaScript 作为内嵌的脚本语言,比如 Adobe 公司的著名 PDF 阅读器 Acrobat、Linux 桌面环境 GNOME 3。

(6)跨平台的桌面应用程序

Chromium OS、Windows 8 等操作系统直接支持 JavaScript 编写应用程序。Mozilla 的 Open Web Apps 项目、Google 的 Chrome App 项目、GitHub 的 Electron 项目、以及 TideSDK 项目,都可以用来编写运行于 Windows、Mac OS 和 Android 等多个桌面平台的程序,不依赖浏览器。

著名程序员 Jeff Atwood 甚至提出了一条 “Atwood 定律”

“所有可以用 JavaScript 编写的程序,最终都会出现 JavaScript 的版本。”(Any application that can be written in JavaScript will eventually be written in JavaScript.)

三.>>JavaScript 与 Java 的关系<< 

四.基本语法

1.变量

  1. 如果变量赋值的时候,忘了写var命令,这条语句也是有效的, 但不利于表达意图,而且容易不知不觉地创建全局变量,所以建议总是使用var命令声明变量。
  2. 一个变量没有声明就直接使用,JavaScript 会报错,告诉你变量未定义。
  3. JavaScript 是一种动态类型语言,也就是说,变量的类型没有限制,变量可以随时更改类型。
  4. 使用var重新声明一个已经存在的变量,是无效的, 但是,如果第二次声明的时候还进行了赋值,则会覆盖掉前面的值
var a = 1;
// 基本等同
a = 1;


x
// ReferenceError: x is not defined


var a = 1;
a = 'hello';


var x = 1;
var x;//无效
x // 1

          5.变量提升:JavaScript 引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行, 这造成所有变量的声明语句都会被提升到代码的头部

console.log(a);
var a = 1;
//等同于👇👇👇,不会报错

var a;
console.log(a);
a = 1;

>>undefined //表示变量a已声明,但还未赋值

标识符(最常见的为变量)命名规则

  • 第一个字符,可以是任意 Unicode 字母(包括英文字母和其他语言的字母),以及美元符号($)和下划线(_)。
  • 第二个字符及后面的字符,除了 Unicode 字母、美元符号和下划线,还可以用数字0-9

//合法的标识符

arg0
_tmp
$elem
π
//不合法的标识符

1a  // 第一个字符不能是数字
23  // 同上
***  // 标识符不能包含星号
a+b  // 标识符不能包含加号
-d  // 标识符不能包含减号或连词线

中文是合法的标识符,可以用作变量名。 

var 临时变量 = 1;

JavaScript 保留关键字 :

(不可以用作变量、标签或者函数名。有些保留关键字是作为 Javascript 以后扩展使用)

abstractargumentsbooleanbreakbyte
casecatchcharclass*const
continuedebuggerdefaultdeletedo
doubleelseenum*evalexport*
extends*falsefinalfinallyfloat
forfunctiongotoifimplements
import*ininstanceofintinterface
letlongnativenewnull
packageprivateprotectedpublicreturn
shortstaticsuper*switchsynchronized
thisthrowthrowstransienttrue
trytypeofvarvoidvolatile
whilewithyield

* 标记的关键字是 ECMAScript5 中新添加的。

 2.注释

//略略略

/*
    略略略
*/


兼容HTML的注释
<!-- 略略略 -->

3.区块

{
  var a = 1;
}

a // 1
//在区块内部,使用var命令声明并赋值了变量a,然后在区块外部,变量a依然有效
//区块对于var命令不构成单独的作用域
//往往用来构成其他更复杂的语法结构,比如for、if、while、function等。

4.if()   if...else语句  switch语句 while语句 for语句(与C语言语法基本相同)

等值比较优先使用===,!==   而非 ==   !=  

(这意味着比较时不会发生类型转换。)

补充:switch语句部分和case语句部分,都可以使用表达式。 

switch (1 + 3) {
  case 2 + 2:
    f();
    break;
  default:
    other();
}

三元运算符


(条件) ? 表达式1 : 表达式2

//利用三元运算符,输出相应的提示。
var m;
console.log(
  m ?
  'm is a boy' :
  'm is a girl'
)

>>m is a girl


//利用三元运算符,可以在字符串之中插入不同的值。
var msg = '数字' + n + '是' + (n % 2 === 0 ? '偶数' : '奇数');

标签(相当于定位符)

(搭配break continue跳出特定的循环)

芜湖,这样就可以干脆利落地跳出或者重新执行双重循环咯!!!

如果break/continue语句后面不使用标签,则只能进入下一轮的内层循环。


actionA:
  for (..; ...; ...){
    for (...; ...; ...){
      if (...) break actionA;
      f();
    }
  }


actionB:
  for (..; ...; ...){
    for (...; ...; ...){
      if (...) continue actionB;
      f();
    }
  }

也可以用于跳出代码块

block: {
  console.log(1);
  break block;
  console.log('本行不会输出');
}
console.log(2);

// 1
// 2

五.数据类型

详见MDN>>JavaScript 数据类型和数据结构<<

JavaScript 有三种方法,可以确定一个值到底是什么类型。

  • typeof运算符
  • instanceof运算符
  • Object.prototype.toString方法

最新的 ECMAScript 标准定义了 8 种数据类型:

   1. 6 种原始类型,使用 typeof 运算符检查:

  •         undefined
  •         Boolean
  •         Number
  •         String
  •         BigInt
  •         Symbol 

    2.null

    3.Object

任何 constructed 对象实例的特殊非数据结构类型,也用做数据结构:new Object,new Array,new Map,new Set,new WeakMap,new WeakSet,new Date,和几乎所有通过 new keyword 创建的东西。

记住 typeof 操作符的唯一目的就是检查数据类型,如果我们希望检查任何从 Object 派生出来的结构类型,使用 typeof 是不起作用的,因为总是会得到 "object"。检查 Object 种类的合适方式是使用 instanceof 关键字。但即使这样也存在误差。

typeof 123      // "number"
typeof '123'    // "string"
typeof false    // "boolean"

function f() {}
typeof f        // "function"

typeof undefined// "undefined"

v    // ReferenceError: v is not defined
typeof v        // "undefined"

如果变量v没有用var命令声明,直接使用就会报错。但是,放在typeof后面,就不报错了,而是返回undefined,实际编程中,这个特点通常用在判断语句

// 错误的写法
if (v) {
  // ...
}
// ReferenceError: v is not defined

// 正确的写法
if (typeof v === "undefined") {
  // ...
}

undefined典型场景

一.变量声明了,但没有赋值
var i;
i // undefined

二.调用函数时,应该提供的参数没有提供,该参数等于 undefined
function f(x) {
  return x;
}
f() // undefined

三.对象没有赋值的属性
var  o = new Object();
o.p // undefined

四.函数没有返回值时,默认返回 undefined
function f() {}
f() // undefined

布尔值

如果 JavaScript 预期某个位置应该是布尔值,会将该位置上现有的值自动转为布尔值。转换规则是除了下面六个值被转为false,其他值都视为true

  • undefined
  • null
  • false
  • 0
  • NaN
  • ""''(空字符串)
if ('') {
    console.log('none-empty');
}else{
    console.log('empty');
}

// empty

注意,空数组([])和空对象({})对应的布尔值,都是true 

if ([]) {
  console.log('true');
}
// true

if ({}) {
  console.log('true');
}
// true

对象

属性可以动态创建,不必在对象声明时就指定。

链式引用

var o1 = {};
var o2 = { 
    option: f(x) {
        return x+100;
    } 
};

o1.foo = o2;
o1.foo.option(55) // 155

对象的引用

var o1 = {};
var o2 = o1;

o1.a = 1;
o2.b = 2;

o1.b // 2
o2.a // 1

o1和o2指向同一个对象,因此为其中任何一个变量添加属性,另一个变量都可以读写该属性。

此时,如果取消某一个变量对于原对象的引用,不会影响到另一个变量。

var o1 = {};
var o2 = o1;

o1 = 1;
o2 // {}

o1和o2指向同一个对象,然后o1的值变为1,这时不会对o2产生影响,o2还是指向原来的那个对象。

但是,这种引用只局限于对象,如果两个变量指向同一个原始类型的值。那么,变量这时都是值的拷贝。
如:
var x = 1;
var y = x;

x = 2;
y // 1
y和x并不是指向同一个内存地址。

查看一个对象本身的所有属性,可以使用Object.keys方法。

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

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

delete命令用于删除对象的属性,删除成功后返回true。删除一个不存在的属性,delete不报错,而且返回true

var obj = { p: 1 };
Object.keys(obj) // ["p"]

delete obj.p // true
obj.p // undefined
Object.keys(obj) // []


var obj2 = {};
delete obj2.p // true

该属性存在,且不得删除时 , delete命令会返回false 

delete命令只能删除对象本身的属性,无法删除继承的属性

var obj = Object.defineProperty({}, 'p', {
  value: 123,
  configurable: false
});

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

//toString是对象obj继承的属性,虽然delete命令返回true,
//但该属性并没有被删除,依然存在。这个例子还说明,
//即使delete返回true,该属性依然可能读取到值
delete obj.toString // true
obj.toString // function toString() { [native code] }

for...in循环用来遍历一个对象的全部属性(会跳过不可遍历的属性)

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

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语句的一个很大的弊病,就是绑定对象不明确。
单纯从代码块根本无法判断是全局变量,还是对象obj的一个属性

with (obj) {
  console.log(x);
}


因此,建议不要使用with语句,可以考虑用一个临时变量代替with。

with(obj1.obj2.obj3) {
  console.log(p1 + p2);
}

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

函数

函数声明

  • 函数的表达式需要在语句的结尾加上分号,表示语句结束。而函数的声明在结尾的大括号后面不用加分号
  • 如果同一个函数被多次声明,后面的声明就会覆盖前面的声明

(1)function 命令

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

(2)函数表达式

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

采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。

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

x
// ReferenceError: x is not defined

print()
// function

上面代码在函数表达式中,加入了函数名x。这个x只在函数体内部可用,指代函数表达式本身,其他地方都不可用。这种写法的用处有两个,一是可以在函数体内部调用自身,二是方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)。因此,下面的形式声明函数也非常常见。

var f = function f() {};

 (3)Function 构造函数(非常不直观,几乎无人使用)

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

// 等同于
function add(x, y) {
  return x + y;
}

只有最后一个参数会被当做函数体,其他参数都是add函数的参数,
如果只有一个参数,该参数就是函数体。

var foo = new Function(
  'return "hello world";'
);

// 等同于
function foo() {
  return 'hello world';
}

>>函数名的提升<<

name属性

function f1() {}
f1.name // "f1"
----------
var f2 = function () {};
f2.name // "f2"
----------
var f3 = function myName() {};
f3.name // 'myName'

----------
name属性的一个用处,就是获取参数函数的名字, 从而知道传入的参数是什么函数。
var myFunc = function () {};

function test(f) {
  console.log(f.name);
}

test(myFunc) // myFunc

length 属性(定义时的参数个数)

function f(a, b) {}
f.length // 2

length属性提供了一种机制,判断定义时和调用时参数的差异,以便实现面向对象编程的“方法重载”(overload)。

函数本身的作用域

函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。

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

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

f() // 1

x是在函数f的外部声明的,所以它的作用域绑定外层,
内部变量a不会到函数f体内取值,所以输出1,而不是2

易错点:

如果函数A调用函数B,却没考虑到函数B不会引用函数A的内部变量。

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

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

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

闭包

即能够读取其他函数内部变量的函数.

示例:

function f1() {
  var n = 999;
  function f2() {
    console.log(n);
  }
  return f2;
}

var result = f1();
result(); // 999

在 JavaScript 语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,比如f2记住了它诞生的环境f1,所以从子函数f2可以得到f1的内部变量。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包用处:

  • 读取外层函数内部的变量
  • 让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在,闭包使得内部变量记住上一次调用时的运算结果(类似C++中的类)
  • 封装对象的私有属性和私有方法 
function createIncrementor(start) {
  return function () {
    return start++;
  };
}

var inc = createIncrementor(5);

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

start是函数createIncrementor的内部变量, 通过闭包,start的状态被保留

那为什么闭包能够返回外层函数的内部变量?原因是闭包(上例的inc)用到了外层变量(start),导致外层函数(createIncrementor)不能从内存释放。只要闭包没有被垃圾回收机制清除,外层函数提供的运行环境也不会被清除,它的内部变量就始终保存着当前值,供闭包读取。

所以闭包可以看作是函数内部作用域的一个接口

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

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

作用:

在定义函数之后,立即调用该函数

好处:

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

 数组

任何类型的数据,都可以放入数组 

var arr = [
  {a: 1},
  [1, 2, 3],
  function() {return true;}
];

arr[0] // Object {a: 1}
arr[1] // [1, 2, 3]
arr[2] // function (){return true;}

 如果数组的元素还是数组,就形成了多维数组

var a = [[1, 2], [3, 4]];
a[0][1] // 2
a[1][1] // 4

清空数组的一个有效方法,就是将length属性设为0

var arr = [ 'a', 'b', 'c' ];
arr.length // 3

arr.length = 2;
arr // ["a", "b"]
arr.length = 0;
arr // []

由于数组本质上是一种对象,所以可以为数组添加属性,但是这不影响length属性的值

将数组的键分别设为字符串和小数,结果都不影响length属性。

var a = [];

a['p'] = 'abc';
a.length // 0

a[2.1] = 'abc';
a.length // 0
因为length属性的值就是等于最大的数字键加1,
而这个数组没有整数键,所以length属性保持为0

>>类似数组的对象<<

posted @ 2021-08-03 12:35  泥烟  阅读(28)  评论(0编辑  收藏  举报