JavaScript 读书笔记
JS 的类型
内置类型:string、boolean、number、null、undefined、symbol、object
;
除了对象 object 外,其余统称为“根本类型”
null 有时会被当作一种对象类型,然而这其实只是语言自身的一个 bug,即对 null 执行 typeof null 时会返回字符串 “object”
内置对象
- String
- Number
- Boolean
- Object
- Function
- Array
- Date
- RegExp
-
Error
- 这些内置函数能够当作构造函数来应用,从而能够结构一个对应子类型的新对象
null
和undefined
没有对应的结构模式,它们只有文字模式。相同,Date 只有结构,没有
使 JS 对象不可变的办法
- 应用
Object.defineProperty
的属性形容,将writable
和configurable
设置false
- 如果你想禁止一个对象增加新属性并且保留已有属性,能够应用
Object.prevent Extensions(..)
, 但能够批改属性的值 Object.seal(..)
会创立一个“密封”的对象, 密封之后不仅不能增加新属性,也不能重新配置或者删除任何现有属性,但能够批改属性的值Object.freeze(..)
会创立一个解冻对象,领有下面的个性,且无奈批改它们的值,这个办法是能够利用在对象上的级别最高的不可变性,它会禁止对于对象自身及其任意间接属性的批改
下面四种办法有个独特的毛病:对象属性援用的其余对象是不受影响的,仍然能够批改,和 const 定义数组一样,尽管不能批改数组的指向,但能够向数组 push,pop 等操作
for..in 和 Object.keys()区别
Object.keys()
办法会返回一个由一个给定对象的本身可枚举属性组成的数组,数组中属性名的排列程序和应用for...in
循环遍历该对象时返回的程序统一。- 两者之间最次要的区别就是
Object.keys()
不会走原型链,而for..in
会走原型链;
对象的属性描述符
Object.defineProperty
什么是闭包
当函数能够记住并拜访所在的词法作用域,即便函数是在以后词法作用域之外执行,这时就产生了闭包。
函数的作用域
函数作用域是基于代码的作用域嵌套,而不是调用栈,换句话能够了解为,函数外部的作用域作用域定义在函数定义的中央,如下代码
function foo() {console.log( a); // 2
}
function bar() {
var a = 3
foo();}
var a = 2; bar();
this 的指向
this
是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调 用时的各种条件。this
的绑定和函数申明的地位没有任何关系,只取决于函数的调用形式。
默认绑定
间接应用不带任何润饰的函数援用进行调用的(不蕴含箭头函数),this
指向全局对象,通常为 window
或undefined
(严格模式下)
function foo() {console.log( this.a);
}
var a = 2; // 理论为 window.a = 2
foo(); // 2
隐式绑定
this
指向调用地位的上下文对象,调用援用链很长时,指向调用的最初一层
function foo() {console.log( this.a);
}
var obj = {
a: 2,
inner: {
a: 3,
foo: foo
}
};
obj.inner.foo(); // 3
此时 this 是指向 foo 的调用上下文 obj.inner,this.a === obj.inner.a
显式绑定
咱们罕用的 apply,call,bind
等办法,apply
的第一个是 this
指向的对象,第二参数是数组,传递给调用函数的参数,call
的第一个是 this
指向的对象,前面的所有参数都会传递给要调用的函数,bind
的第一个是 this
指向的对象,之外的其余参数都传给函数进行柯里化
new 绑定
- 创立 (或者说结构) 一个全新的对象。
- 这个新对象会被执行原连贯。
- 这个新对象会绑定到函数调用的
this
。 - 如果函数没有返回其余对象,那么
new
表达式中的函数调用会主动返回这个新对象。
判断 this 指向(不含箭头函数)
-
函数是否在
new
中调用(new
绑定)? 如果是的话this
绑定的是新创建的对象。var bar = new foo()
-
函数是否通过
call、apply
(显式绑定)或者硬绑定调用? 如果是的话,this 绑定的是 指定的对象。var bar = foo.call(obj2)
-
函数是否在某个上下文对象中调用(隐式绑定)? 如果是的话,
this
绑定的是那个上 下文对象。var bar = obj1.foo()
-
如果都不是的话,应用默认绑定。如果在严格模式下,就绑定到
undefined
,否则绑定到 全局对象。var bar = foo()
被疏忽的 this
如果你把 null
或者 undefined
作为 this
的绑定对象传入 call
、apply
或者 bind
,这些值在调用时会被疏忽,理论利用的是默认绑定规定,个别应用在函数不关怀 this
指向时,依然须要传入一个占位值,这时 null 可能是一个不错的抉择
function foo() {console.log( this.a);
}
var a = 2;
foo.call(null); // 2
function foo(a,b) {console.log( "a:" + a + ", b:" + b);
}
// 把数组“开展”成参数
foo.apply(null, [2, 3] ); // a:2, b:3
// 应用 bind(..) 进行柯里化
var bar = foo.bind(null, 2); bar(3); // a:2, b:3
箭头函数的 this 指向
箭头函数不应用 this
的四种规范规定,而是依据外层 (函数或者全局) 作用域来决定 this
。
function foo() {
// 返回一个箭头函数
return (a) => {//this 继承自 foo()
console.log(this.a);
};
}
var obj1 = {a:2};
var obj2 = {a:3};
var bar = foo.call(obj1);
bar.call(obj2); // 2, 不是 3 !
foo() 外部创立的箭头函数会捕捉调用时 foo() 的 this。因为 foo() 的 this 绑定到 obj1,bar(援用箭头函数)的 this 也会绑定到 obj1,箭头函数的绑定无奈被批改。(new 也不行!)
this 的指向深刻了解
看上面一段代码
let A = {
b: 1,
B: {
b:2,
fun1: function() {return () => {console.log(this)
}
},
fun2: () => {console.log(this)
},
fun3: function () {console.log(this)
}
}
}
var t1 = A.B.fun1()
A.B.fun1()() // this 指向 A.B
t1() // this 指向 A.B
var t2 = A.B.fun2
A.B.fun2() // this 指向 window
t2() // this 指向 window
var t3 = A.B.fun3
A.B.fun3() // this 指向 A.B
t3() this 指向 window
第一种调用函数返回箭头函数模式,this
应该指向调用 fun1
的调用者,确认之后 this
不会扭转,能够看到 A.B
调用 fun1()
生成了箭头函数t1
,此时 this
就确认为了指向 A.B
,且不会扭转,A.B.fun1()()
能够看成 (A.B.func1())()
,和下面解释一样
第二种调用,因为 fun2
生成的箭头函数不在一个函数内,因而 this
确认指向全局对象 window
,不论他如何调用
第三种调用就属于咱们常见的一般函数调用,this
指向函数的调用这,即 A.B.fun3()
当然指向 A.B
,t3()
相当于 window.t3()
, 因而 this
指向 window
对象的属性屏蔽
通常状况下,咱们认为 在原型链下层曾经存在的属性赋值时,就肯定会触发屏蔽原型链的雷同属性
,尽管在日常开发中,个别是没错的,但如果波及到原型链上对属性的形容时,就不肯定正确了
这时会呈现三种状况
- 如果在
[[Prototype]]
链下层存在名为 foo 的一般数据拜访属性 (参见第 3 章) 并且没 有被标记为只读(writable:false)
,那就会间接在 myObject 中增加一个名为 foo 的新 属性,它是屏蔽属性。 - 如果在
[[Prototype]]
链下层存在 foo,然而它被标记为只读(writable:false)
,那么 无奈批改已有属性或者在 myObject 上创立屏蔽属性。如果运行在严格模式下,代码会 抛出一个谬误。否则,这条赋值语句会被疏忽。总之,不会产生屏蔽。 - 如果在
[[Prototype]]
链下层存在 foo 并且它是一个setter
,那就肯定会调用这个 setter。foo 不会被增加到(或者说屏蔽于)myObject,也不会从新定义 foo 这 个setter
。
罪魁祸首是因为赋值运算符“=”,如果你心愿在第二种和第三种状况下也屏蔽 foo,那就不能应用 = 操作符来赋值,而是应用 Object.defineProperty(..)来向 myObject 增加 foo
有时候也会触发隐式屏蔽
var anotherObject = {a:2};
var myObject = Object.create(anotherObject);
anotherObject.a; // 2
myObject.a; // 2 应用原型链属性
myObject.a++; // 理论运行 myObject.a = myObject.a + 1 合乎下面第一种状况,因而增加个了个屏蔽原型链属性 a
myObject.a; // 3
罕用的论断和办法
- 对象的
可枚举
就相当于能够呈现在对象属性的遍历中
- ES6 中的符号
Symbol.iterator
来获取对象的@@iterator
外部属性 typeof
有一个非凡的平安防备机制,typeof
一个未声明的变量显示 undefined- 变量没有类型,但它们持有的值有类型。类型定义了值的行为特色。
delete
运算符能够将单元从数组中删除,单元删除后,数组的 length 属性并不会发生变化toPrecision(..)
办法用来指定无效数位的显示位数:- 最大整数是
2^53 - 1
,即9007199254740991
,最 小 整 数 是-9007199254740991
,如果大于 64 位须要转化为字符串 - 检测一个值是否是整数,能够应用 ES6 中的
Number.isInteger(..)
办法 - 能应用
==
和===
时就尽量不要应用Object.is(..)
,因为前者效率更高、更为通用。Object.is(..) 次要用来解决那些非凡的相等比拟。 Symbol(..)
原生构造函数来自定义符号,不能带new
关键字,否则会出错~~~~
将类数组转化为数组的办法
Array.prototype.slice.call(arguments)
Array.from(arguments)
构造函数 constructor 的了解
Foo.prototype
的 .constructor
属性只是 Foo
函数在申明时的默认属性。如果你创立了一个新对象并替换了函数默认的 .prototype
对象援用,那么新对象并不会主动取得 .constructor
属性,须要通过原型链查找。当然,你能够给 Foo.prototype
增加一个 .constructor
属性,不过这须要手动增加一个合乎失常行为的不可枚举属性。
.constructor
是一个十分不牢靠并且不平安的援用,稍不留神 .constructor
就可能会指向你意想不到的中央。
function Foo() { /* .. */}
Foo.prototype = {/* .. */}; // 创立一个新原型对象
var a1 = new Foo();
a1.constructor === Foo; // false!
a1.constructor === Object; // true
批改 Function.prototype
-
Bar.prototype = Foo.prototype;
毛病:当 你 执 行 类 似 `Bar.prototype. myLabel = ...` 的赋值语句时会间接批改 `Foo.prototype ` 对象自身
-
Bar.prototype = new Foo();
毛病:调用构造函数可能会想 `this` 增加数据属性
-
Bar.ptototype = Object.create(Foo.prototype);
ES6 之前须要摈弃默认的 `Bar.prototype`
-
Object.setPrototypeOf(Bar.prototype, Foo.prototype);
ES6 开始能够间接批改现有的 `Bar.prototype`
instanceof 操作符
a instanceof Foo
在 a 的整条 [[Prototype]]
链中是否有指向 Foo.prototype 的对象
迭代器的了解
ES 新增 API
Array.of
生成数组Array.from(..)
类数组转化为数组Array.find
在数组中搜寻一个值Array.fill(..)
数组填充Array.copyWithin(..)
数组复制Array.findIndex(..)
查找数组一个值得索引- 原型办法 entries()、values()、keys()
Object.is(..)
执行比 === 比拟更严格的值比拟Object.getOwnPropertySymbols(..)
间接从对象上获得所有的符号属性Object.setPrototypeOf(..)
设置对象原型Object.assign(..)
对象浅拷贝
new.target 可能指向调用 new 的指标结构器
class Parent {constructor() {if (new.target === Parent) {console.log( "Parent instantiated");
}
else {console.log( "A child instantiated");
}
}
}
class Child extends Parent {}
var a = new Parent();
// Parent instantiated
var b = new Child();
// A child instantiated
公开符号 Symbol
对于符号的一些属性,笔者只能通过例子来了解
Symbol.iterator
集体了解,通过执行 obj[Symbol.iterator]()
生成一个迭代器来判断 for .. of
的运行,如下
var arr = [4, 5, 6, 7, 8, 9];
for (var v of arr) {console.log(v);
}
// 4 5 6 7 8 9
// 定义一个只在奇数索引值产生值的迭代器
arr[Symbol.iterator] = function* () {
var idx = 1;
do {yield this[idx];
} while ((idx += 2) < this.length);
};
for (var v of arr) {console.log(v);
}
// 5 7 9
第一步执行 for (var v of arr)
执行了 arr[Symbol.iterator]
生成一个迭代器,通过调用 this.next()
var list = {
a: 1,
[Symbol.iterator]() {return this},
next() {if (this.a < 5) {
this.a++
return {
value: this.a,
done: true
}
}
return {
value: this.a,
done: false
}
}
}
var i = 0
for (var item of list) {console.log(item)
if (i > 5) break;
i++
}
// 2 3 4 5
集体了解
通过下面比拟能够认为一个对象只有通过 [Symbol.iterator]
生成一个符合要求的迭代器,对应第一种,或者一个对象有 [Symbol.iterator]
和 next
对应第二种,就能够调用 for ... of
进行迭代
Symbol.toStringTag 与 Symbol.hasInstance
Symbol.toStringTag
相当于扭转 Object.prototype.toString
失去的后果 [object xxx]
中的 xxx
Symbol.hasInstance
批改子例是否是实例行为个性
function Foo(greeting) {this.greeting = greeting;}
Foo.prototype[Symbol.toStringTag] = "Foo";
Object.defineProperty( Foo, Symbol.hasInstance, {value: function(inst) {return inst.greeting == "hello";}
} );
var a = new Foo("hello"),
b = new Foo("world");
b[Symbol.toStringTag] = "cool";
a.toString(); // [object Foo]
String(b); // [object cool]
a instanceof Foo; // true
b instanceof Foo; // false
Symbol.toPrimitive
任意对象值上作为属性的符号 @@toPrimitivesymbol 都能够通过指定一个 办法来定制这个 ToPrimitive 强制转换
var arr = [1, 2, 3, 4, 5];
arr + 10; // 1,2,3,4,510
arr[Symbol.toPrimitive] = function (hint) {if (hint == "default" || hint == "number") {
// 求所有数字之和
return this.reduce(function (acc, curr) {return acc + curr;}, 0);
}
};
arr + 10; // 25