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