概述
目录
- 一、介绍
- 二、语法
- 2.1、变量
- 2.1.1、变量命名
- 2.1.2、变量声明
- 2.2、类型
- 2.2.1、Numbers
- 2.2.2、Strings
- 2.2.3、其他类型
- 2.3、操作符
- 2.4、控制语句
- 2.5、Arrays
- 2.6、Objects
- 2.6.1、literal
- 2.6.2、JSON
- 2.6.3 解构(Destructuring)
- 2.6.4 其他
- 2.6.4.1 Object.assign()
- 2.7、Functions
- 2.7.1 apply()和call()
- 2.7.2 函数声明
- 2.8、对象实例化与继承
- 2.8.1、this
- 2.8.2、prototype
- 2.8.3、继承
- 2.9、闭包
- 2.10 strict mode
- 2.11 class
- 2.12、其他
- 2.12.1、注释
- 2.12.2 模板字符串(Template literals)
- 2.13 模块
- 2.13.1 模块导出(export)
- 2.13.2 模块导入(import)
- 2.13.3 html中引入
- 三、浏览器相关
- 3.1、浏览器解析过程
- 3.2、引入javascript
- 3.3、javascript加载问题
- 3.4、DOM
- 3.4.1 Window
- 3.5、事件
- 3.5.1、介绍
- 3.5.2、使用
- 3.5.3、Event object
- 3.5.4、Event bubbling and capture
- 3.5.5、事件代理
- 3.6 存储
- 四、其他
- 4.1、一些概念
- 参考
一、介绍
html用于定义网页的结构,css设置网页格式和外观,javascript则添加交互内容。
在浏览器的环境下,javascript并不仅仅指的语言,它包括了:核心语言(ECMAScript)和Web APIs(包括DOM) 。
- 核心语言(ECMAScript):ECMAScript只定义了语言规则,不仅仅用于浏览器环境,还可以用于服务端脚本,如node.js。javascript几乎实现了ECMAScript所有的规范,包括:
- Language syntax (parsing rules, keywords, control flow, object literal initialization, …)
- Error handling mechanisms (throw, try/catch, ability to create user-defined Error types)
- Types (boolean, number, string, function, object, …)
- The global object. In a browser, this global object is the window object, but ECMAScript only defines the APIs not specific to browsers, e.g. parseInt, parseFloat, decodeURI, encodeURI…
- A prototype-based inheritance mechanism
- Built-in objects and functions (JSON, Math, Array.prototype methods, Object introspection methods, etc.)
- Strict mode (see here)
- DOM APIs:浏览器环境中提供的api。其中DOM代表文档,可以通过DOM与文档交互。还包括一些其他的api,比如XMLHttpRequest、Canvas 2D Context相关的api。
众所周知,,DOM在所有浏览器中差异性很大,一些特性不兼容。因此通过一些可靠的、跨浏览器兼容的库来抽象DOM特性,比如JQuery、prototype和YUI。
参考:
JavaScript technologies overview
Introduction
二、语法
这里开始介绍javascript的核心语法,即ECMAScritp的内容,奈何它的语法太多了,这里只介绍了入门所需要的知识。
javascript是一种面向对象、动态类型、函数式编程等等等语言。声明变量时不需要指定类型,一切皆为对象,不论是数组、还是函数,都是一种特殊的对象。所谓对象,即有方法有属性的变量。在javascript并没有类,但是有prototype机制,因此照样可以继承其他对象的属性和方法,也因此可以通过构造函数来生成对象。构造函数和普通函数并没有什么区别,只是在使用new关键值时被当做了构造函数。函数也是一种对象,但可以储存代码,在其他对象中传递,因此会出现闭包的概念。
一切皆为对象,那么在全局作用域内定义的变量和函数属于谁?ECMAScript规定了一个全局对象,属于该对象的属性和方法。ECMAScript定义了全局变量的接口,由所处的环境提供该变量,通过该对象操作环境,在浏览器的环境中为window。
注意!!ECMAScript和环境无关,只是语言,比如javascript就实现了ECMAScript的所有语法,是js的一部分;node.js也是使用ECMAScript作为脚本语言;在qt中,ECMAScript也被实现过。等等等。
参考:
A re-introduction to JavaScript (JS tutorial)
2.1、变量
变量是用来存储数据的容器,可以是任何类型数据。javascript是动态类型语言(也称弱类型语言),声明变量时不需要指定数据类型,但通过操作符typeof
可以打印变量类型。
变量必须被声明才能被使用,否则报错。声明变量没有赋初始值时,则默认undefined
。
2.1.1、变量命名
javascript支持unicode字符集,变量也可以使用中文来命名。但是命名最好还是要符合一定规范,下面给出的不是强制的,但最好准守:
- 使用数字(0-9)、字母(a-z,A-Z)和下划线命名。
- 不要以下划线、数字开头。下划线开始的变量名有特殊意义。
- 最好使用驼峰命名法,即变量和方法的首个单词小写,之后的每个单词首字母大写。方法第一个单词最好是动词。
- 不要使用保留字和DOM对象的名字。
大小写敏感。
2.1.2、变量声明
javascript中使用var
、let
和const
来定义变量。
var
声明的变量
- 没有block scope(块作用域),只有function scope;
- 并且声明有hoisting现象,但初始化没有该现象;
- 可以多次声明变量。
hoisting表示任何地方的变量声明都会被放到作用域内最顶端。例子如下:
bla=2;//由于hoisting的存在,下面的声明会跑到作用域顶端,因此可以对比变量赋值
var bla;
console.log(a);//undefined。即使声明会hoisting,但是初始化不会,因此还是未赋值状态。
var a=3;
var x=1;
{
var x=2;//允许多次声明变量
}
console.log(x);//2,因为没有块作用域
//var变量i没有块作用域,因此这里可以访问到
for(var i=0;i<10;i++){
//i这里可以访问
}
//i这里可以访问
注意,函数声明也存在hosting现象,几乎没有块作用域,但是block作用域会限制函数声明hosting到该block作用域的顶端。但要注意,函数名是隐性的变量名。如:
myfun1();//函数声明也可hosting,因此可调用
function myfun1(){...};
foo();//错误,因为函数声明被限制在块作用域顶端
foo;//undefined,因为函数名是一个隐性变量,不受块作用域影响,因此相当于被声明了但没赋值。
{
function foo(){...}
foo();//正确
}
foo();//几乎没有块作用域,现在能够看到函数声明了
看到了吧,var即复杂,又和我们使用的习惯不一样(对于c++,java程序员来说),因此后面又出现了let
和const
关键值来声明变量。
let
有块作用域,没有hoisting现象,和c++、java声明的变量一样。如:
a=3;//错误,未定义不能使用。
let a;
a=4;//正确
//i不能在这访问
for(let i=0;i<10;i++){
//i可以在这访问
}
//i不能在这访问
const
与let类似,但声明的时候必须初始化,并且不能被修改。
2.2、类型
大致分为如下:
- Six data types that are primitives:
- Boolean
- Null
- Undefined
- Number
- String
- Symbol (new in ECMAScript 6)
- Object
- Function
- Array
- Date
- RegExp
在javascript中,尽管一切皆为对象,但是它还是有六种基本类型的。。并且值是不可变的,传参时是按值传递,而不是按引用传递!!
除了null和undefined,其它基本类型都有对应的包装对象,可以调用它的实例方法。如:
"tom".toUpperCase();//"TOM"
(2).toString();"2"
注意,typeof null;//“object” 。。。
2.2.1、Numbers
根据说明书,数值都是用双精度浮点数表示的,但是实际上整数还是被当做32位int。但也不用担心3/2=1
问题的出现,并且整数可以使用按位运算。
一些内置函数可以将字符转化为Number,如parseInt()
,parseFloat()
。+
,Number()
也可以将字符转化为数值。如:
parseInt('123', 10); // 123
parseInt('010', 10); // 10,忽略0,不会当做八进制
parseInt('0x10'); // 16 ,会当作二进制
parseInt('11', 2); // 3,指定二进制
+ '42';
// 42
+ '010';
// 10,同样,不当作八进制。。
+ '0x10'; // 16
Number('123');//123
运算过程中会出现一些特殊值:NaN(Not a Number)、Infinity
(无穷大)和-Infinity
(负无穷大),如:
parseInt('hello', 10); // NaN
1 / 0; //
Infinity
-1 / 0; // -Infinity
参考:
Number
2.2.2、Strings
使用utf-16的code units编码,因此一个字符1或2个code units编码,并且length属性以code unit为单位。
字符串用单引号或双引号围起来,单引号内可以存在双引号,反之亦然。特殊字符也可以通过转义,成为普通字符。+可以连接字符串与字符串,字符串与数值。Number()函数可将字符串转化为数值。数值变量的toString()方法可将数值转化为字符串。
一切皆为对象,string也为对象,有一些属性和方法可使用:
- length:得到字符串长度。
- []:通过下标获取某个字符。
- indexof():找到子串出现的开始位置。
- slice():通过索引截取子串。
- toLowerCase(),toUpperCase():获得全小写或大写的字符串。
- replace():将字符串中的子串替换成其他的子串。
参考:
String
2.2.3、其他类型
null
表示故意设置的、不存在的值;undefined
表示变量还未初始化,函数没有返回值时也会返回undefined。
任何值都可以被转化为布尔值,根据如下规则:
- false, 0, empty strings (""), NaN, null, and undefined all become false.称为falsy
- All other values become true.称为truthy
Boolean()可以显示转化值为布尔值,但是很少使用,因为会自动转化(java中不会)。
2.3、操作符
完整操作符内容可以参考:Expressions and operators
+
可以用来做字符串连接:
'hello' + ' world'; // "hello world"
'3' + 4 + 5;
// "345" ,连接之前先转换成字符串
3 + 4 + '5'; // "75"
比较运算符要注意===
,!==
与==
,!=
的区别,前者同时比较变量的类型和值,而后者仅仅比较值。
//比较前会先转换成同一类型
123 == '123'; // true
1 == true; // true
//类型不同,false
123 === '123'; // false
1 === true;
// false
&&
和||
使用短路逻辑,即是否执行第二个条件取决于第一个。如:
- 访问对象属性之前检查该对象是否为null:
var name = o && o.getName();
- 当值是falsy,则赋值一个:
var name = cachedName || (cachedName = getName());
typeof
可以打印变量类型,返回的string形式。
2.4、控制语句
完整控制语句参考:Loops and iteration
语句有:if else、while、do-while、for、switch。
要注意,block语句(即{})可以看做一个语句。
for语句有两种变种:
- for…in:用于遍历对象所有可遍历属性。
for (let property in object) { // do something with object property }
- for…of:用于遍历array的值。
for (let value of array) { // do something with value }
switch中,表达式和case值之间是通过===
来比较的。
三元运算符:
condition ? exprT : exprF
2.5、Arrays
数组是一个可以存入多个任意类型值的对象。下标访问、赋值,从0开始。
//create array
var random = ['tree', 795, [0, 1, 2]];
var shopping = ['bread', 'milk', 'cheese', 'hummus', 'noodles'];
//access array
shopping[0];
//modify array items
shopping[0]='tahini';
//access two-dimensional array
random[2][2];
length
属性实际上是最大索引+1,有时候不表示数组实际个数。
访问不存在数组,返回undefined。可以使用for...of
遍历数组。
一些有用的数组方法:
- String的split()方法分割字符串产生数组
- Array的join()方法合并数组成为字符串
- Array的toString()方法将数组转化为字符串
- push()和pop()方法分别在数组底端添加和删除数组。
- unshift()和shift()方法分别在数组前端添加和删除数组。
splice
用于在某个索引上添加或删除元素。Array.from
:浅拷贝
2.6、Objects
对象是一组属性和方法的集合。其实对象就是键值对的集合,键是名字,值可以是任意类型,比如函数、对象、数组等。
对象可以通过构造函数或对象字面值(object literal)创建。如:
//构造函数
var obj = new Object();
//object literal
var obj = {
name: 'Carrot',
for: 'Max', // 'for' is a reserved word, use '_for' instead.
details: {
color: 'orange',
size: 12
}
};
通过Dot notation或Bracket notation访问对象,也可以创建新的属性或方法。Bracket notation最为灵活,通过字符串来访问属性或方法,甚至使用变量提供变量名。
- Dot notation
//access person.age //set person.age=2323; //set non-exist property or method person.newProperty="i'm a new property"; person.newFun=function(){...};
- Bracket notation
//access person['age']; //set person['age']=2222; //set non-exist method person['newFun']=function(){...}; //set property dynamically by using variable let propName="name"; person[propName]=....
2.6.1、literal
上面已经谈到过了对象字面值,这里深入一下。
literal由零个或多个键值对的列表组成,被花括号包含起来。属性名可以是标识符、数字或字符串,值可以是任意类型对象或其字面值。
var person = {
name: ['Bob', 'Smith'],
age: 32,
gender: 'male',
interests: ['music', 'skiing'],
bio: function() {
alert(this.name[0] + ' ' + this.name[1] + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.');
},
greeting: function() {
alert('Hi! I'm ' + this.name[0] + '.');
}
};
如果属性名不是合理的标识符或数子,则必须使用字符串表示属性名,然后通过bracket notation([ ])来访问该属性。如:
var unusualPropertyNames = {
'': 'An empty string',
'!': 'Bang!'
}
console.log(unusualPropertyNames['']);
// An empty string
console.log(unusualPropertyNames['!']); // Bang!
object literal还支持:
- setting the prototype at construction
- shorthand for
foo: foo
assignments - defining methods
- making super calls
- computing property names with expressions
如:
var obj = {
// __proto__
__proto__: theProtoObj,
// Shorthand for ‘handler: handler’
handler,
// Methods
toString() {
// Super calls
return 'd ' + super.toString();
},
// Computed (dynamic) property names
[ 'prop_' + (() => 42)() ]: 42
};
参考:
Object literals
2.6.2、JSON
尽管JSON已经不是Object的内容了,但是由于JSON是基于Object literal表示法的,因此很有必要在这里谈一下。
JSON的完整语法可以参考:Full JSON syntax
部分语法如下:
JSON = null
or true or false
or JSONNumber
or JSONString
or JSONObject
or JSONArray
可见值的类型可以是对象、数组、数值、字符串、boolean和null。
JSON和literal的一些区别如下:
- 属性名必须使用双引号的字符串
- 不能有方法
- 除了上面的类型外,其他的可以用字符串表示,比如Date.toJSON()返回日期的字符串表示。
- 数值的首个数字不能为0,小数后面必须接一个数字。
…
这些区别在JSON的语法中都可以清楚的看出来。
2.6.3 解构(Destructuring)
解构,即将数组的元素或对象的属性赋值到单个变量中。数组按位置赋值,对象按变量名赋值,如下所示:
//数据安装位置赋值
var a, b, rest;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]
//对象按照变量名赋值
({ a, b } = { a: 10, b: 20 });//需要小括号,防止{a,b}被解析为块语句
console.log(a); // 10
console.log(b); // 20
({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}
参考:Destructuring assignment
2.6.4 其他
2.6.4.1 Object.assign()
将源对象的可遍历属性拷贝给目标对象,如果目标对象有相同属性,则被覆盖。属于浅拷贝。
语法:
Object.assign(target, ...sources)
参数:
- target:目标对象
- sources:源对象
返回值:
- 目标对象
2.7、Functions
简单的一个函数:
function add(x, y) {
var total = x + y;
return total;
}
- 函数可以接收0到多个参数,如果调用时没有传入参数,那么参数默认为undefined
- 如果函数定义时没有参数列表,但是调用时传入了参数,可以通过arguments对象访问这些对象。
- return用来返回值并结束函数,可以直接
return;
不返回值但结束函数。如果函数没有返回值时,函数默认返回undefined。 - 函数的定义会引入函数作用域。
arguments是一个类似array的对象,存有所有传入函数的参数。可以通过下标访问参数,有length属性。也可通过for of语句遍历:
function add(){
var sum=0;
for(let value of arguments){
sum+=value;
}
return sum;
}
add(2,3,4,5);//14
Rest parameter syntax允许接收无限个参数,然后存入到一个数组中。
//除了第1、2个参数外,其他参数组合构建成一个数组存入theArgs中。
function f(a, b, ...theArgs) {
// ...
}
spread operator用于函数调用时将数组展开成逗号分隔的参数,如avg(...numbers)
匿名函数,没有方法名,作为一个表达式,最好添加分号在语句尾。
var avg=function(){...};
2.7.1 apply()和call()
预备知识:2.8.1 this
都是Function.prototype的方法,即所有函数可调用。这两个可以方法指定this
和arguments
来调用函数。apply()
和call()
在于,apply()
的arguments
参数接收array-like对象。
语法:
function.apply([thisArg, [argsArray]])
参数:
thisArg
:函数调用的this
值。如果没有给定,在non-strict mode中,会自动包装(auto box)为调用该方法的对象;strict mode中,不会自动包装,this
值为undefined
argsArray
:传给函数调用的参数,array-like对象。
bind()
方法返回该函数的拷贝,除了this
指针指向被指定的值。
参考:Search Function.prototype.apply()
2.7.2 函数声明
函数有两种方法定义:函数声明和函数表达式
函数声明:
function a(){}
函数表达式(匿名函数):
var a=function(){}
实际上,函数表达式也可以拥有名字,但只能在函数内部使用,通过函数name
属性取出。但感觉没啥子用处。
class可以说是一种特殊的函数,也有上述两种定义方式。
对象中的方法定义可以使用简写,见2.6.1小节
2.8、对象实例化与继承
JavaScript是基于原型(prototype)的语言,没有class语句的存在。不过现在有了class语句了,但只是prototype的语法糖罢了,这里不介绍或以后介绍了。
除了对象字面值可以直接建立对象,还可以通过构造函数来实例化对象。构造函数实例化的方式很复杂,首先先理解几个概念。
2.8.1、this
this
关键词,在函数中通常为undefined
,当然也可以使用call()
、apply()
方法手动设置this
值;在对象方法中指向该对象。由于自动包装(auto box)的存在,undefined
或null
值将被替换为全局对象(如window
),primitive
值被包装成对象。但在strict mode下,不会发生自动包装。
下面看看两个非strict模式下自动包装的例子:
var s={first:"Simon",
last:"Willison",
fullName:function(){
return this.first+' '+this.last
}
};
s.fullName();//"Simon Willison" ,此时this指向s
var fullName=s.fullName;
fullName();//undefined undefined ,fullName是全局变量window的方法,因此this指向window
function test(){
test.a="aaa";
function innerTest(){
console.log(this.a)//因为this还是指向全局变量window,因此输出undefined
}
innerTest();
}
test();
参考:Does javascript autobox?
2.8.2、prototype
每个对象都有一个prototype object(原型对象),被存入到对象的__proto__属性中。prototype object的所有属性和方法都会被该对象继承。而prototype object对象源自于构造函数(constructor function)的prototype属性,该属性也是一个对象,Object。prototype object中有个属性constructor,指向它的构造函数。
对象调用属性或方法的过程,以调用方法为例:
- 先查看该对象是否有该方法,有则执行该方法。
- 如果没有,则查看对象的__proto__属性,看是否有对应方法,如果有则执行,否则继续查看__proto__的__proto__属性,直到达到prototype chain底部。
- 执行时要注意,this指向的是调用该函数的对象。
重申一下new
关键字的过程:
- new后面的函数被当作构造函数,因此先创建一个Object对象,构造函数中 this指向该对象。
- 为构造函数创建prototype属性(一个Object对象);prototype对象中创建一个constructor属性,指向该构造函数。
- 将构造函数的prototype属性赋值该新对象的__proto__属性上。
- 执行构造函数。
- 返回该对象。
每个被构造函数实例化的对象的__proto__都引用构造函数的prototype属性,因此实际上prototype object被所有对象共享。例如,里面声明的方法只有一份。因此,当添加一个新的方法到构造函数的prototype属性上,所有对象都能够使用该方法。
理解了上面的过程,也就理解构造函数实例化对象的过程。举个例子:
function Person(first, last) {
this.first = first;
this.last = last;
}
Person.prototype.fullName = function() {
return this.first + ' ' + this.last;
};
Person.prototype.fullNameReversed = function() {
return this.last + ', ' + this.first;
};
//instantiation
let person1=new Person("Simon","Willsion");
person1.fullName();//"Simon Willsion"
实例化person1的过程中,new
关键字创建了一个对象,构造函数中的this指向了该对象。构造函数中对该对象添加了两个属性。同时该对象的__proto__属性指向了构造函数的prototype属性。然后返回对象给person1变量。person1调用fullName时,person1自身没有方法fullName,查看了它的prototype object,即__proto__属性,发现有该函数,于是调用。由于person1是调用该函数的对象,因此fullName方法中的this正确的指向了person1,然后返回正确结果。
前面的例子是一个很好的模板,不建议在构造函数中为构造函数的prototype属性添加方法,如果这样,那么每次实例化对象都会创建新的方法然后覆盖原方法上,影响效率。
2.8.3、继承
javascript中的继承是对象之间的继承,即一个对象继承另一个对象所有属性,作为对象的属性;而原型方法(即构造函数prototype属性中定义的方法)则构成一个prototype chain。
一般对象是通过构造函数来创建的,因此对象之间的继承通过改写构造函数来完成。用到了几个方法:
function.call(thisArg, arg1, arg2, ...)
:在thisArg的上下文中调用函数。此时this会指向拥有该上下文的对象,即thisArg。Object.create()
:创建新对象,参数中的对象作为新对象的原型对象(prototype object)。
具体例子或模板:
function Person(...){
//实例化后对象的属性
this....=...
...
}
//实例化后对象的方法
Person.prototype....=function(...){...}
...
function Teacher(...){
//在this上下文中执行构造函数Person,即Person函数中的this指向Teacher实例化的对象。
Person.call(this,....);
//创建实例化后对象的属性,必须在后面,才能覆盖Person函数创建的属性。
this....=...
...
}
//prototye指向新对象,该新对象的__proto__属性指向Person.prototyep,这样构成了一个原型链。
Teacher.prototype=Object.create(Person.prototype);
//新创建的对象没有该属性,需要自己创建。
Teacher.prototype.constructor=Teacher;
//实例化后对象的原型方法
Teacher.prototype....=function(...)...
...
看不懂?呵呵。。我觉得我过几天自己都看不懂。。。
2.9、闭包
函数中还可以定义函数,而且可以访问外部函数的变量。如:
function parentFunc() {
var a = 1;
function nestedFunc() {
var b = 4; // parentFunc can't use this
return a + b;
}
return nestedFunc(); // 5
}
但是下面呢?
function makeAdder(a) {
return function(b) {
return a + b;
};
}
var x = makeAdder(5);
var y = makeAdder(20);
x(6); // ?
y(7); // ?
这里涉及一个概念:作用域对象(scope object)。每当函数开始执行时,一个新的作用域对象就会被创建,这个对象会保存所有在函数内创建的变量(包括参数)。不像全局对象(可以通过this访问,在浏览器中为window),这些作用域对象不能被直接访问。
因此,当makeAdder()函数被调用时,作用域对象也被创建。即使makeAdder()函数结束后,scope对象也不会消失,因为被创建的内部函数没有结束,仍有引用指向scope对象的引用,因此垃圾回收器不会回收该对象。
于是:
x(6); // returns 11
y(7); // returns 27
2.10 strict mode
strict mode限制了一些javascript语法的使用,并且赋予了普通代码新的含义。strict mode的一些规则或作用:
- 一些错误(silent errors)会被忽视,但strict mode抛出该错误
- 更好的优化效率
- 一些在以后ECMAScript版本中使用的语法或word会被保留
- 不能对未定义变量赋值。
- 不能删除不可配置属性
- 作用域:将
use strict;
写在脚本最顶部,则作用到全局作用域中。但是多个脚本拼接后,use strict
未位于顶部,则失效。将use strict
写在函数顶部,则为函数作用域,函数中的函数也会在strict mode中。 - 函数中this,见2.8.1小结
- 非non-strict mode中,可以通过修改
arguments
来修改函数中的命令参数;但strict mode中,arguments中保存了一份命令参数的拷贝,因此修改不能作用到命令参数中了。 - 对
eval
做出了一些修改,略。。。 - 略!!!。。
参考:
- Transitioning to strict mode
- Strict mode
2.11 class
类只是javascript原型继承链的语法糖,并没有引入新的对象继承模型。因此可以参考2.8小节来理解。
- 类定义:类实际上是一个特殊的函数,因此可以与函数一样,通过class声明、class表达式定义。但定义的class没有hoisting现象。
- strict mode:class的body处于strict mode中,即使实例对象将原型、静态方法赋值给其他变量或对象,该方法也处于strict mode中。
- 组成:class中由构造函数
constructor
、原型方法、静态方法、字段声明(目前处于实验阶段,建议不用)组成。- 构造方法:一般用于定义字段,有且只能有一个;
- 原型方法:实例对象后,原型方法会位于对象的
__proto__
中; - 静态方法:就是class这个“特殊”的函数(亦为对象)的自己的方法啦,不会出现在实例对象的原型链中,因此不能被实例对象访问。
this
、super
:构造方法、原型方法中应该使用this来引用其字段;super
在子类中引用父类。使用准则:一般常在构造方法中使用this
创建字段,在原型方法中引用字段;常使用super()
调用父类构造函数。- 字段声明:目前字段声明处于实验阶段,因此这里只提一提。首先,字段声明在class body内前端,可以有默认值。
- 公有字段:使类具有自我描述性,仅此而已。
class Rectangle { height = 0; width; constructor(height, width) { this.height = height; this.width = width; } }
- 私有字段:类外使用不被允许,且不被看到。但必须在body内前端声明,否则之后再创建也会被认作普通属性。
class Rectangle { //私有字段必须存在,且#开头 #height = 0; #width; constructor(height, width) { this.#height = height; this.#width = width; } }
- 公有字段:使类具有自我描述性,仅此而已。
- 继承:使用
extends
继承父类,然后构造constructor
顶部使用super()
调用父类构造函数创建父类的实例属性。
继承的结果:子类实例对象获得子类和父类的实例属性,子类与父类的原型方法构成原型链,存入实例对象的prototype对象中(即__proto__
),然后子类的可以覆盖父类的。与2.8.3小节实现并无啥子差别。因为继承也是语法糖,也可以通过一定手段继承传统的构造函数,略!
例子:
class Animal {
//构造方法
constructor(name) {
//实例属性
this.name = name;
}
//原型方法
speak() {
console.log(this.name + ' makes a noise.');
}
//静态方法
static staticMethod(){
console.log("i'm static method in Animal class");
}
}
//class自己的属性
Animal.aaaa="i'm normal property for Animal because it's a special function or object";
//把class当作构造函数。。
Animal.prototype.bbbb=function(){
console.log("alse could consider it as traditional constructor");
}
//继承
class Dog extends Animal {
constructor(name) {
//调用父类构造函数,创建父类实例属性
super(name);
//也可以创建子类实例属性,覆盖父类的
//this.name="tom";
}
speak() {
console.log(this.name + ' barks.');
}
}
let d = new Dog('Mitzie');
d.speak(); // Mitzie barks.
//类也是一种对象或方法,它自己的方法不会出现在实例对象的原型链中;它自己的属性不会出现在实例对象的属性中
Animal.staticMethod();
是否注意到了,class中方法定义的不同?class中方法定义使用简写方式,见2.6.1,且只能这样定义!
参考:Classes
2.12、其他
2.12.1、注释
- 单行注释
// I am a comment
- 多行注释
/* I am also a comment */
2.12.2 模板字符串(Template literals)
模板字符串(Template)允许在字符串中嵌入表达式,语法如下:
`string text`
`string text line 1
string text line 2`
`string text ${expression} string text`
tag `string text ${expression} string text`
最后一个才是最全的写法,tag是一个自定义的函数名,它处理模板字符串,默认行为是将这些部分concatenate(组装)成一个字符串。
执行过程:表达式${expression}
将模板字符串分割成多个部分(parts),执行表达式,所有部分(parts)全都传入函数tag中,返回函数处理结果。
tag函数编写,略!
参考:Template literals
2.13 模块
模块(module)和脚本(script)类似,都是一个含有JavaScript代码的文件,但还有一些不同。
- 模块中只有被
export
(导出)的部分,才能在其他模块import
(导入)并使用,import()
可以动态导入。 - 模块默认处于strict mode
- 模块默认使用
defer
方式异步加载,关于defer
见JavaScript入门3.3小节 import
可以在模块中使用,也能在html的内嵌脚本中使用,但必须加上type="module"
属性;但import()
可以在普通脚本中使用;export
只能在模块中使用。- 引入的模块,受同源政策限制。(因此有引入模块的html代码最好在服务器下打开)
- html中多次引入模块,只执行一次。
习惯使用后缀
.mjs
区分普通js文件
2.13.1 模块导出(export)
模块写好后,需要使用export
导出允许其他模块使用的部分。部分语法如下:
//导出变量、方法、类,指定名字
export { name1, name2, …, nameN };//导出变量、方法、类,name为其名字
export let name1, name2, …, nameN; // 导出变量,也可以使用var const
export function FunctionName(){...}//导出函数
export class ClassName {...}//导出类
//导出默认部分,因此可以不给出名字
export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };
//重定向,即导出其他模块的内容
export * from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
export { default } from …;
例子如:
test.js
export const repeat = (string) => `${string} ${string}`;
export function shout(string) {
return `${string.toUpperCase()}!`;
}
export default "aaa";
2.13.2 模块导入(import)
使用import
导入其他模块export的部分。部分语法如下:
import defaultExport from "module-name";//导入模块默认export部分,defaultExport是赋予该默认部分的名字。
import * as name from "module-name";//导入模块所有被export的部分,并放入名字空间name中
import { export } from "module-name";//导入模块exprot部分
import { export as alias } from "module-name";//导入同时换名字
import { export1 , export2 } from "module-name";//导入模块多个export部分
import { foo , bar } from "module-name/path/to/specific/un-exported/file";//???
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export [ , [...] ] } from "module-name";//同时导入模块默认export部分和export部分。
import defaultExport, * as name from "module-name";
import "module-name";//执行模块中的全局代码,但不引入任何值。貌似在Webpack中还能引入静态资源、如img、css
var promise = import("module-name"); // This is a stage 3 proposal.动态导入,以后研究
注意,module-name必须是绝对地址,或以/
,./
, ../
开始的相对地址。
例子如下:
index.js
import bb, {repeat, shout} from './test.js';
console.log(repeat('hello'));
// → 'hello hello'
console.log(shout('Modules in action'));
// → 'MODULES IN ACTION!'
console.log(bb);
2.13.3 html中引入
模块最后需要在html引入,如下所示:
<script type="module" src="main.mjs"></script>
<script nomodule src="fallback.js"></script>
如果引入的是模块,必须指定属性type="module"
。第二个script
元素是用来兼容不支持模块的浏览器的,但浏览器支持模块时,nomodule
属性的script
会不被执行。
也可以在内嵌脚本中引入模块:
<script type="module">
import bb, {repeat, shout} from './test.js';
</script>
参考:
- Using JavaScript modules on the web
- MDN import
- MDN export
- MDN script
三、浏览器相关
3.1、浏览器解析过程
浏览器有三个解析器:html parser,javascript parser 和 css parser。
html解析器最先开始运行,可以调用其他两个解析器。从上到下按照顺序解析每个元素标记,生成元素、注册到DOM名字空间中、属性添加到dom元素节点上。
如果遇到css,则加载并调用css解析器解析。此过程与html解析器同时进行,非阻塞,因为css规则会一直被应用。这是为什么新定义的css规定能够导致元素重画的原因。
如果遇到javascrpt,则加载并调用javascript解析器运行。会导致html解析器停止运行、阻塞,直到javascript运行完毕。
参考:https://stackoverflow.com/questions/1795438/load-and-execution-sequence-of-a-web-page
3.2、引入javascript
在html中引入javascript中有三种方式:
- internal javascript
<script> // JavaScript goes here </script>
- external javascript
<script src="script.js"></script>
- inline javascript handles
<button onclick="createParagraph()">Click me!</button>
3.3、javascript加载问题
问题:
- javascript在加载并解析时会导致html解析器停止运行,导致网页的加载性能下降。
- javascript在加载并运行时,html元素并未全部被解析,因此js操作元素会出错。
办法:
script
元素放到网页最底部,仅仅</bdy>
标签前面。解决第一个问题。- 脚本中监听window的onload事件,文档全部加载完毕才执行js。解决第二个问题。
- 使用script的async或defer属性。async异步加载,但是加载完后执行时还是会阻塞html解析器,能解决第一个问题;defer异步加载,但是在html解析完毕后、document的onload事件出发前执行js,同时能直接两个问题。
注意:有async属性的script只能是外部链接,而且执行顺序不确定;而defer执行顺序与源码一致。由于defer脚本是在onload事件前执行的,因此此时css和图片可能仍在加载与解析。
参考:https://flaviocopes.com/javascript-async-defer/
3.4、DOM
DOM(Document Object Model)是一个用于操作HTML或XML文档的编程接口。DOM将文档的内容当作节点以树的方式表示文档。并且是与语言无关的,DOM只定义了每个接口的内容。
DOM接口大致分为两类:DOM核心接口和与html元素相关的接口。DOM核心接口定义了一些主要和DOM文档操作、事件相关的接口。其中Node
接口代表节点,Element
接口继承Node,代表元素;而其他接口定义了和html元素相关的接口,含有对应元素特定的属性。其中HTMLELement
代表html元素,然后是继承该接口的其他更具体的接口,比如HTMLTableElement
。
在实际使用中,都是对对象操作的,那对象与接口的关系如何?接口与接口之间是有继承关系的,因此对象可以使用多个接口定义的属性或方法。比如<p>
对应的接口HTMLParagraphElement
,该接口可以设置和p元素相关的属性;它的继承对象为HTMLElement
,该接口拥有操作html元素通用的方法;在之上为Element
、Node
,提供了操作dom节点的方法。经常使用的还是window和document对象,前者代表浏览器,后者代表文档,提供了查找某个元素的方法。
下面给出一些常用的方法:
- Document.querySelector():通过css选择器来选择html元素。
- Document.querySelectorAll()
- Document.getElementById()
- Document.getElementsByTagName()
- Document.createElement()
- Node.appendChild()
- Node.removeChild()
- Element.innerHTML
- Node.textContent
- element.style.left
- element.setAttribute()
- element.getAttribute()
- element.addEventListener()
- window.content
- window.onload
- console.log()
- window.scrollTo()
参考:
Document Object Model (DOM)
3.4.1 Window
Window接口表示一个包含了DOM文档的窗口,在浏览器中具体表现为一个标签页。Window接口的对象window含有所有和窗口相关的内容,也是JavaScript的运行环境,JavaScript中的全局变量、函数就是该window对象的属性或方法;除了DOM和脚本外,还涉及其他对象,如location(代表链接)、History(代表理事)等等。
具体使用参考
- Window MDN
- Wndow w3schools
3.5、事件
3.5.1、介绍
事件是系统(browser)中某个动作或事件的发生,系统会产生一个信号(event),这个信号含有关于该事件的信息。随着信号的产生,事件处理器或监听器会采取一定动作处理该事件。这种事件处理机制称为事件模型。事件模型不是javascript特有的,不同的上下文中会有不同的事件模型,即事件模型的实现机制不太一样,比如web api、插件、Node.js等等上下文。
这里讲的事件模型是DOM相关的,所有的事件都在DOM中发生,比如用户产生的(鼠标、键盘事件),或者被API产生(动画结束、视频暂停等)。也能够通过代码触发事件,如HTMLElement.click()
模拟点击事件;或者自定义事件,通过EventTarget.dispatchEvent()
发送给某个元素。Event
是最泛化的事件接口,其他接口都继承该接口。
许多DOM元素都可以监听事件,然后执行元素的事件处理器。通过EventTarget.addEventListener()
可以让事件处理器附着在元素上,并且同一事件还可以同时被多个处理器监听。removeEventListener()
则可以删除连接的事件。
事件传播一般是从DOM树中从下至上传播的,即bubbling。由于同一事件可能有多个处理器,因此事件被传到一个元素时,会横向传播,然后在向上传播(bubbling)。一些元素都有对事件有默认行为,如链接点击会打开网页、表单按钮点击发送表单等等,默认行为会在事件横向传播时执行,通过Event.preventDefault()
可以阻止默认行为。Event.stopPropagation()
可以阻止事件向上传播,但允许横向传播。Event.stopImmediatePropagation()
同时阻止事件横向和向上传播。
参考:https://developer.mozilla.org/en-US/docs/Web/API/Event
3.5.2、使用
一共有三种方法可以使用事件:
- Event handler properties:html元素对应的javascript对象存在一些用于执行事件处理器的属性。
var btn = document.querySelector('button'); btn.onclick = function() {...};
- Inline event handlers:不建议使用。html元素的一些属性可以指定事件处理的javascript代码块。
<button onclick="alert('Hello, this is my old-fashioned event handler!');">Press me</button>
- addEventListener() and removeEventListener():前者可以为事件注册事件监听器且同时能注册多个,后者能够删除事件监听器。
//add handler btn.addEventListener('click', bgChange); //remove handler btn.removeEventListener('click', bgChange); //add multiple handlers myElement.addEventListener('click', functionA); myElement.addEventListener('click', functionB);
参考:
全局事件处理器
所有event
事件的所有键值key
3.5.3、Event object
事件发生后,会同时产生event对象,通过该对象可以获得关于事件的信息,比如Event.target指定具体产生event的对象。
事件处理器都可以接收event对象,只要函数定义时指定参数即可:
let button=document.querySelector('div');
button.addEventListener('click',function(event){
console.log(event.target);
});
Event
对象是一个泛型类,含有事件通用的方法和属性。每个具体的事件还有它特有的属性方法。
下面是Event
的有用的属性和方法:
- event.target – the deepest element that originated the event.
- event.currentTarget (=this) – the current element that handles the event (the one that has the handler on it)
- event.eventPhase – the current phase (capturing=1, bubbling=3).
- event.stopPropagation():事件停止向上传播,但在当前元素内可以纵向传播给同一事件的其他处理器。
- event.stopImmediatePropagation():停止事件横向、纵向传播。
- event.defaultPrevented():阻止默认行为。注意,在html元素的事件处理属性中
return false
也能阻止默认行为,但是在其他地方行不通,因此不建议使用。
3.5.4、Event bubbling and capture
当事件发生时,event对象会传播给目标对象,执行处理器。但是这个过程分为三个阶段:
- Capturing phase – the event goes down to the element.
- Target phase – the event reached the target element.
- Bubbling phase – the event bubbles up from the element.
默认处理器时注册在bubbling阶段的,因此先执行target对象的处理器,然后执行顶层元素的处理器。
addEventListener的第三个参数为true时(默认false)注册在capturing阶段。
target阶段不能单独存在,而是包含在其他两个阶段内。比如,capturing阶段最后一个处理器和budding阶段第一个处理器就发生在该阶段。
3.5.5、事件代理
这里的代理就是指父类来处理子类的事件。比如点击li
,但监听器设置在ul
上,通过event.target就知道具体哪个元素被点击,于是可以方便其代为处理该事件。利用事件传播原理。
参考:https://javascript.info/bubbling-and-capturing
3.6 存储
html5后引入了web storage(本地储存),比cookies更好用。本地存储分两类:
window.localStorage
- stores data with no expiration datewindow.sessionStorage
- stores data for one session (data is lost when the browser tab is closed)
使用setItem
保存键值对,getItem
获得键值对。注意,键值对被存为字符串形式,因此存入和取出时做好转化。
参考:HTML5 Web Storage
四、其他
4.1、一些概念
- javascript:语言
- Browser APIs:内置在浏览器的api
- Third party APIs:构建在三方平台上的api。
- javascript libraries:通常为含有自定义函数的javascript文件,比如Jquery、React、Mootools。
- javascript frameworks:高于libraries,为html、css、js和其他技术的集合。也libraries的区别是框架是控制反转的,即框架调用开发者的代码。
错误大致类型:语法错误和逻辑错误。console.log()函数用于输出数据到控制台。
- javascript单线程,异步
参考
A re-introduction to JavaScript (JS tutorial)
JavaScript
Web APIs
DOM
javascript reference
最后
以上就是大方咖啡豆为你收集整理的javascript入门一、介绍二、语法三、浏览器相关四、其他参考的全部内容,希望文章能够帮你解决javascript入门一、介绍二、语法三、浏览器相关四、其他参考所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复