0.该随笔灵感主要来源于阮总的开源博客
阮一峰 JavaScript 教程
主要涵盖了js发展历史中比较重要的语法,以及一部分ES5的内容
1.入门篇
1.1 for循环
js对象为对象类型时候,for-in 遍历的是其键名,for-of 将抛出异常:TypeError: obj is not iterable
js对象为数组类型时候,for-in 遍历的是其索引(string), for-of遍历其元素(可以是对象)
准确的来是说,不仅仅是索引,这种方式甚至可以获取其原型链的属性
2.数据类型
2.1 对象
2.1.1 对象取值
js对象为对象类型,obj[‘键名’],注意必须带有’’,或者obj.键名
2.2 数组
2.2.1 in运算符
适用于对象,也适用于数组
var arr = [];
arr[2] = 'a';
2 in arr // true
'2' in arr // true
1 in arr //false ,这里是数组的空位
2.2.2 数组的空位
当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位(hole)。
数组
空位的值被读取时候,会被看做undefined
使用delete arr[index]指令删除元素的值,将形成空位,并不会影响数组的length属性
数组的forEach()、for-in、Object.keys()将跳过空位的执行,如果某个位置值为undefined,将不会被跳过
2.2.3 类数组对象
如果一个对象的所有键名都是正整数或零,并且有length属性,称为“类似数组的对象”(array-like object)。
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
2.2.4 slice()
slice()(切面)比substring()更加强大
数组的slice方法可以将“类似数组的对象”变成真正的数组。
Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({length: 1}) // [undefined]
可以使用Array.prototype.forEach等数组的原型方法,但是效率比原生数组慢
3.运算符
3.1 一些容易混淆的关键字
typeof运算符 判断对象的类型
instanceof运算符 用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链上
in 运算符 无论该属性是自身的还是继承的,都为true
4.语法专题
4.1 数据类型的转换
4.1.1 Number类型的转换
运算自动转换、new (…)、parseFloat(…)
5.标准库
5.1 Object
Object是所有对象的父类
5.1.1 delete 指令
可以删除对象的值,使其再次读取时,返回undefined
5.1.2 静态api & 实例api
Object本身的方法(相当于java类中的静态属性),通过类名调用
Object.print
Object实例的方法(相当于java中可以被继承的实例方法),通过实例变量名 或 原型对象 调用
Object.prototype.xxx
5.1.3 静态api
getOwnPropertyNames() //用法同keys(),同时支持不可枚举的属性(该对象的描述属性、属性的描述属性)
hasOwnProperty() //自身是否持有该属性(非继承)
getOwnPropertyDescriptor(对象,属性名) //获取某个属性的描述对象,无法获取继承而来的属性
preventExtensions() 禁用对象的扩展 isExtensible()
seal() 禁用对象的配置(新增+修改属性的描述属freeze() 禁用对象的扩展、修改、删除 isFrozen()
以上三种方法可以通过在原型对象上修改属性,然后在当前对象中调用getPrototypeOf()获取原型对象读取修改 来绕过限制。
当属性是一个引用的时候,并不能限制对这个引用的属性的操作
create() 通过模板创建一个新对象实例
5.1.5 实例api
valueOf() 自动类型转换时候默认调用,默认返回对象本身
toString() toLocaleString() 字符串运算时候默认调用
默认输出一个带有构造函数名称的字符串“[Object 构造函数名称(也是对象的类型)]”
当toString()被覆写的时候,使用Object.prototype.toString.call(value),将调用原始的toString()
hasOwnProperty() 该实例对象是否具有这个属性,继承而来的属性不算
isPrototypeOf() 是否为该对象的原型对象propertyIsEnumerable() 某个属性是否可枚举
get/setPrototypeOf() 返回原型对象
5.1.6 构造方法
var obj = Object(…) //参数可以为null或undefined
var obj = new Object //构造函数,等同var obj = {}
两者语法相似,但是语义不同
5.2 属性描述对象
JavaScript 提供了一个内部数据结构,用来描述对象的属性,控制它的行为这个内部数据结构称为“属性描述对象”(attributes object)。
5.2.1 元属性
{
value: ?, //只要writable或configurable为true,即可被修改
writable: boolean, //如果get/set不为undefined,那么这里只允许为false,此时定义calue也将报错
enumerable: boolean, //可遍历,若false,for-in、.keys 、JOSN.stringify将无法获取这些属性
configurable: boolean, //可修改该属性的描述属性(除value)+删除该属性
get: function,
set: function
}
configurable为false,只允许writable由true变false,不允许false->true
5.2.2 存取器
get/set的函数getter/setter统称存取器(accessor)
//1.写法一:当obj.p时将调用get/set
var obj = Object.defineProperty({}, 'p', {
get: function () {return 'getter';},
set: function (value) {console.log('setter: ' + value);}
});
//(更常用)写法二:这种写法的configuarble和enumerable属性为true(写法一,false)
var obj = {
get p() {return 'getter';},
set p(value) {console.log('setter: ' + value);}
};
存取器相关的属性使用obj[属性名]拷贝只会拷贝其值,可使用Object.defineProperty来拷贝get/set方法属性
5.3 Array
5.3.1 数组的创建方法
var a = new Array(3); //通过构造
var b = [undefined, undefined, undefined]; //通过字面量
构造出来的空数组没有 “键名”,二者的Length属性都可用
5.3.2 数组类型的判断
Array.isArray(arg)
5.3.3 数组api
js的数组像是java的Deque双向队列,不仅仅是数组,可做栈、队列
pop() push() 栈方法,将改变原来的数组
shift()、unshift() 队列方法,也会改变原数组
join() 使用一个分割符将该数组的所有元素“组成”一个字符串返回
Array.prototype.join.call() 在字符串和类数组对象上实现了一样的能力
concat() 在一个数组尾部追加一个数组,不改变原数组
reverse() 简单的不谈,改变原数组
slice() ... , 不改变
sort(func(m,n)) 默认不传的话,按字典序,改变
map(func(n)) 改变
forEach(func(ele,[index],[array])) 不改变,只操作数据
filter(func(n)) 不改变
indexOf() lastIndexOf 不存在则-1
5.4 包装对象
js原始数组类型数值、字符串、boolean,在一些条件下将自动装箱,转换成 包装类型(Number、String、Boolean)
5.4.1 Boolean
Boolean(只要不为undefined) -true
5.4.2 api
包装对象继承了Object的valueOf()、toString()
5.4.3 自动装箱的场景
'abc'.length // 3 'abc'作为一个字符串不可能有操作自己的函数
5.4.4 自动装箱的尿性(特点)
自动装箱的包装对象在结束调用之后将销毁,下一次调用将重新装箱成另一个新的包装对象->导致该对象的属性只读
如需扩展包装对象的方法,建议通过.prototype.方法名 = … 在其原型对象上操作
5.5 JSON
5.5.1 json数据格式
{ name: "张三", 'age': 32 } // 属性名必须使用双引号
[32, 64, 128, 0xFFF] // 不能使用十六进制值
{ "name": "张三", "age": undefined } // 不能使用 undefined
{ "key": function () {return this.name;}} // 属性值不能使用函数和日期对象
5.5.2 JSON.stringify() & JSON.parse()
JSON.stringify(js对象,[数组,用于指定哪些是需要转换的属性],[格式化json字符串的参数])
当出现非法的值为undefined、函数、xml对象
如果是对象持有的值,该对象将被过滤
如果是数组持有的值,该值将替换成null
值为正则/…/的时候,该值替换成{}
自动过滤掉描述属性enumerable为false的属性
这个用于指定转换属性的数组是有说法,使用前需要查api(可以是函数)
可以在需要转换的对象中声明并实现一个toJSON()方法,一旦声明,stringify()将走toJSON()方法来转换json字符串
JSON.parse(json字符串,[用于指定需要转换的属性的数组/方法])
同上很相似
6.面对对象编程
6.1 实例对象与new命令
6.1.1 new 函数名()
通过构造函数的方式创建对象实例
构造函数建议首字母大写
构造函数的函数体中,this指定将要返回的对象实例
使用new的这个函数是一个普通函数还是一个 构造函数?
这是一个构造函数->函数体中是否声明return?
没有->返回this(即这个函数的实例变量)
有->return 的这个类型是否是对象?
是对象->返回这个对象(这里所指定的对象)
不是对象->返回this
这是一个普通函数(内部是没有this引用)->是否声明return ?
没有->返回{}
有->返回的类型是一个对象吗?
是对象->返回这个对象
不是一个对象->返回{}
new命令的简化流程的代码实现
function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {
// 将 arguments 对象转为数组
var args = [].slice.call(arguments);
// 取出构造函数
var constructor = args.shift();
// 创建一个空对象,继承构造函数的 prototype 属性
var context = Object.create(constructor.prototype);
// 执行构造函数
var result = constructor.apply(context, args);
// 如果返回结果是对象,就直接返回,否则返回 context 对象
return (typeof result === 'object' && result != null) ? result : context;
}
// 实例var actor = _new(Person, '张三', 28);
6.1.2 Object.create(现有对象)
与new的区别:create()不通过构造函数,而是将一个现有的对象作为模板来返回一个新实例对象
返回的这个新实例对象将自动继承前者的属性与方法
6.1.3 函数体中new.target命令
该命令所在的函数是否是通过new命令调用的?
是->new.target指向当前函数
不是->值为undefined
6.2 this
指向运行时所在的环境对象(也可以说是 上一层级,比若说,全局的环境对象即window)
为什么设计this关键字?
js中对象的内地地址 指向了 该对象的属性(可以是方法)的内存地址
不同对象如何存在相同的属性,将引用同一块内存地址,这个该属性的值(假如说是返回一段计算结果)将取决与它运行时的环境(该环境中的同名变量的值)
多层嵌套下,谨慎使用
常见的应用场景
全局…
构造函数->将要返回的实例对象
对象的一个方法属性->该对象
6.2.1 放一个栗子
var a = {
b: {
m: function() {console.log(this.p);},
p: 'Hello'
}
v:'hello',
q:['a1','a2'],
f1: function f1() {this.q.forEach(function (item) {console.log(this.v + ' ' + item);});} ,
f2: function f2() {var that = this; this.q.forEach(function (item) {console.log(that.v + ' ' + item);});} ,
f3: function f3() {this.q.forEach(function (item) {console.log(this.v + ' ' + item);} ,this);} ,
};
//例1
var hello = a.b.m; hello(); //undefined ,this指向m变量所在的环境对象(m变量的上一层级对象),m的函数指向是不是很像是指向了全局的一个匿名函数?
//例2
var hello = a.b; hello.m(); //Hello ,this指向a.b ,这里我们通过改变函数的主调来使得this如我们期望的指向
//例3
a.f1(); // undefined a1 \n undefined a2 ,this指向全局,道理同例1
//例4
a.f2(); // hello a1 \n hello a2 ,通过that保存该函数所在环境对象
//例5
a.f3(); //输出结果同例4 ,通过将函数的this传入forEach()中作为参数
6.2.2 回调中的this
var o = new Object();
o.f = function () {console.log(this === o);}
// jQuery 的写法
$('#button').on('click', o.f);
像这里的this指向的就是按钮标签的dom对象,不再是主调o对象
6.2.3 显示确定this
指定函数执行时的作用域
Object.prototype.方法名.call(作用域对象 ,… 函数传参) & Object.方法名.call(同前者)
如何入参的作用域对象为()、null、undefined,则默认传入全局对象
如果入参的作用域对象为原始数据类型,那么该值将自动装箱成包装对象
Object.prototype.方法名.apply(作用域对象,函数传参的数组) & Object.方法名.apply(同前者)
如果入参的数组中包含形如([‘2’, ,1])这种空元素的话,空元素将被替换成undefined
一个绑定回调的栗子
var o = new Object();
o.f = function () {console.log(this === o);}
var f = function (){
o.f.apply(o);
// 或者 o.f.call(o);
};
// jQuery 的写法
$('#button').on('click', f);
Object.prototype.方法名.bind(作用域对象 ,…函数传参) & Object.方法名.bind(同前者)
与call()、apply() 区别是:可以先绑定后执行,并且不需要在绑定作用域的同时声明函数实现
函数传参可以不传所有参数,可以传入一部分,另一部分在调用的时候传入
该方法每次将返回一个新的函数,如果需要写成匿名的绑定(不做返回值的保存),事件监听中请考虑如何解除绑定
在回调函数中,被调函数中的this将指向环境对象(回调函数对象),因此可以在使用被调函数(被调函数.bind(作用域))
bind()与call()/apply()结合使用,可以起到改变函数的调用方式的作用(比如:直接在原型对象上调用实例对象的方法,具体参考文档)
6.3 对象的继承
ES5基于prototype 原型对象实现
所有对象都有自己的原型对象(除null,Object的原型对象为null)
6.3.1 api
valueOf()
toString()
construvtor 默认指向Prototype所在对象的构造函数
该对象有一个name属性,返回构造函数的名称
修改了原型对象,建议同时修改constructor属性,防止引用出错
6.3.2 原型链
原型对象也是对象,它也有自己的原型对象,因此就形成了一条"原型链"
6.3.3 对象 instanceof 构造函数
表示左边对象是否是右边对象的实例
效果同 构造函数.prototype.isPrototypeOf(对象)
只要对象不为null,就不会失真,原始类型也会失真
需要检查整个原型链
同一个实例对象,可能会对多个构造函数返回true
6.3.4 构造函数的继承
步骤:
1.调用Super父类构造函数
2.让子类的原型指向父类的原型
6.3.5 多重继承
js不支持,但是可以通过Object.assign()绕过限制(Object.assign()使用见文档,主要用于合并js对象)
6.3.6 获取原型对象的方式
obj._ptoto_ //只有浏览器环境下才会部署
obj.constructor.prototype //手动修改原型对象时,可能失效
Object.getPrototypeOf(obj) //建议使用
6.3.7 obj.constructor.prototype 失效的解决方案
同时修改Object.prototype.constructor
//预先声明函数对象设为C的原型对象
var P = function () {};
var p = new P();
var C = function () {};
C.prototype = p; //使C原型对象继承自P对象
C.prototype.constructor = C //将原型对象的构造参数也同时改成C
//执行测试
var c = new C();
c.constructor.prototype === p //true
7.DOM
8.事件
8.1 event对象
浏览器中,事件触发后,封装了该事件相关信息并传递给事件监听函数的一个对象。
8.2 事件传播
冒泡 dom -> event -> 外层dom…
捕获 dom <- event <- 外层dom…
顺序:捕获->冒泡。好像IE不太一样…
8.2.1 传播状态
0,事件目前没有发生。
1,事件目前处于捕获阶段,即处于从祖先节点向目标节点的传播过程中。
2,事件到达目标节点,即Event.target属性指向的那个节点。
3,事件处于冒泡阶段,即处于从目标节点向祖先节点的反向传播过程中。
8.3 元素的的事件监听
8.3.1 this
指向当前监听函数所在的dom元素
8.4 api
bubbles 是否冒泡(只读)
eventPhase 目前所处的传播状态(只读)
cancelable 可否取消(只读)
cancelBubble 可否冒泡,用于阻止事件传播
defaultPrevented 是否调用过preventDefault(),(只读)
currentTarget 当前传播的节点
target 原始事件触发所在的节点
isTrusted 是否人为产生(非脚本触发)
timeStamp
type
preventDefault() 取消当前节点的事件监听函数,不影响传播(节点)
stopPropagation() 阻止事件传播,不包括当前节点
stopImmediatePropagation() 阻止事件传播,包括当前节点
composedPath() 返回一个数组,表示冒泡的节点路径
8.5 一个有趣的现象:浏览器输出event对象的日志中currentTarget=null?
currentTarget是当前的对象,使用console.info输出日志的时候,此时dom的事件传播已经结束了,故null
如果你换做——直接输出console.info(event.currentTarget)则可以成功输出当前的dom对象(event.currentTarget.value可以获取其值)