本来只想简单的记录一下书中知识点,但是写着写着发现this和new书中没有说的很详细。 毕竟这本书的名字叫做《你不知道的Javascript》,而不是《JavaScript高级程序设计》(第四版),所以等后面再写个更详细的。
this
this在js中还是挺神奇的一个东西。
只要记住两点就好了:
-
不要把this用作查找作用域。
-
this指向的对象只取决于函数的调用方式。是我调用的,this就指向我,是你调用的this就指向你。
在实际代码执行的过程中,函数调用会被记录在调用栈中,this就是这个记录的一个属性。
this的绑定规则有四条:默认绑定、隐式绑定、显示绑定、new绑定。
默认绑定:
举个简单的例子来说:
注意: 严格模式下的默认绑定规则,会把this绑定在undefined上。
隐式绑定:
显示绑定
JavaScript提供的绝大多数函数以及你自己创建的所有函数都可以使用call(..)和apply(..)方法来绑定this。
常见的例子:
- 硬绑定,绑定完成之后就无法再修改了
其实在js中已经提供了这种硬绑定的方法就是使用bind
方法:
- API调用的“上下文”
第三方库的许多函数,以及JavaScript语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”(context),其作用和bind(..)一样,确保你的回调函数使用指定的this。
new绑定
不要把js中的new
想象成其他语言中的new
,你可以简单的理解为使用new
来调用一个普通的js函数。
而当你使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行[[Prototype]]连接。
- 这个新对象会绑定到函数调用的this。
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
箭头函数
箭头函数不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this。
箭头函数可以像bind(..)一样确保函数的this被绑定到指定对象。
foo()内部创建的箭头函数会捕获调用时foo()的this。
由于foo()的this绑定到obj1, bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。(new也不行!)
对象
定义对象的方式
类型
属性访问
对象中的key只能是字符串,即使你使用了数字作为键,js还是会转换为字符串。
方法
函数永远不会“属于”某个对象。
即使你在对象的文字形式中声明一个函数表达式,这个函数也不会“属于”这个对象——它们只是对于相同函数对象的多个引用。
数组
数组也是对象,所以虽然每个下标都是整数,你仍然可以给数组添加属性:
注意:如果你给的键是数字那就会改变数组。
浅复制、深复制
js中对所有对象的都是引用,不是复制。具体的内容可以查看我以前写的文章。
最简单的深拷贝实现方式:
属性描述符
这是个大部分人都不会接触也不会用到的内容,但确实是个很重要的东西,在前端重要的响应中就有非常重要的作用。
getter、setter
Object.keys(..)会返回一个数组,包含所有可枚举属性,Object.getOwnPropertyNames(..)会返回一个数组,包含所有属性,无论它们是否可枚举。
in和hasOwnProperty(..)的区别在于是否查找[[Prototype]]链,然而,Object.keys(..)和Object.getOwnPropertyNames(..)都只会查找对象直接包含的属性。
for…in/for…of
for..in循环可以用来遍历对象的可枚举属性列表。
for…of是用来遍历数组的语法,for..of循环首先会向被访问对象请求一个迭代器对象,然后通过调用迭代器对象的next()方法来遍历所有返回值。
类
定义类
首先明确一点,其他语言中的类和JavaScript中的“类”并不一样。类是一种设计模式,JavaScript实现了近似类的功能。
js中使用class
来定义一个类,类可以理解为是一张建造图纸,用图纸造出来的房子就是实例。
一张图纸可以建造出来无数个房子,但是每个房子都是无法区分的,如果你想给房子加扇窗换个门,可以在实例化的时候调用构造函数,来给这个房子做一些变化。
js中把图纸变成房子的行为需要使用操作符new
。
类的内容太多了,感觉这一部分书中讲的不是很好。后面我在补充补充吧。
原型、原型链
关于原型和原型链是什么,可以看我直接写的文章深入理解(图解)js中的原型,原型对象,原型链。
Object.create() 静态方法以一个现有对象作为原型,创建一个新对象,并继承原型链。
题外话:所以如果你想创建一个空对象,并不是let obj={}
,这个时候的obj会有toString()等Object原型链上的方法。而使用let obj = Object.create(null)
,你会得到一个干净的空对象。
ok,那接下来就有一些好玩的事情了:
- 在obj2的原型链上已经有了a属性,这时候在obj2上定义a的时候会创建一个屏蔽属性a,不会直接访问到原型链上的a。
- 如果obj的a属性是writable: false,将会忽略在obj2上的赋值操作,严格模式下会报错。
- 如果上一级的原型链上使用了setter,那么obj2上的a不会被添加到obj上但是会调用setter方法。
如果你能看懂为什么这个obj.a 为什么打印出来是8的话,那还挺牛的。看不懂的话,那就是我的代码写的不好,嘿嘿。
解释一下: 这个get方法中的this虽然写在了obj中,但是在obj2中打印的时候这个this是指向obj2的。你给get和set方法上加上console.log就知道这个调用的时候是怎么回事了。
同样也可以看到,set方法在obj2赋值的时候也执行了。
如果你希望在第二种和第三种情况下也屏蔽a属性,那就不能使用=操作符来赋值,而是使用Object.defineProperty(..)来向obj中添加a。
隐式屏蔽
事件委托
内容太多,记不下来了。。。
看了很多的书之后,发现很多知识并不是你单拎出来一个就能讲明白的,就好比这个原型和原型链。
单看这个知识点看个几遍,就算再绕也能理个差不多,但是仔细想想,那new关键字是干嘛的,Object.create()又是干嘛的,他们都能继承原型链,又怎么区分。
prototype和construct又是什么关系,又比如构造函数是什么,原型链有什么用,等等问题。
这就像十根耳机线缠在一起了,你捋一根线,捋个头出来其实没什么用,因为你再往后面捋就会发现还是一团乱麻。
就像看到了一个果子,找到树枝子,找到树干子,再到树根扎在土里,这个果子才算找到了头,不然这个果子一直悬在一个你说不清的地方。
所以关于一些复杂的点,得写的大一点,写的再多一点。