挑战坚持学习1024天——前端之JavaScript高级
大家好我是牛哥,经过三周的总结大概把基础部分过完一遍,在复盘基础的同时也穿插着复盘了一些js高级的一些内容。在复盘总结时发现知识点与知识点之间是紧密相连的,在实际开发过程中如果但凡有一个知识点不清楚,后续就会比较困难。由此可见js每一个基础知识都是非常重要的。基础知识只是暂时性的告一段落,后续还会持续更新,如有遗漏的还请各位小伙伴指出。学完不代表结束了,还要定期复盘,根据艾宾浩斯遗忘曲线,一周后的记忆存留率仅为25%,唯有利用高效记忆的技巧不断的巩固复习,不断的实践,相信在不久的将来有面试官问到这些知识点时你将脱口而出。希望该系列文章可以帮到大家。
接下来将开启新的一个阶段--js高级部分,吸取了之前的教训发现效率还是不够高,决定接下来提高学习效率,浓缩精华,一起进步!坚持每日精进,坚持不下去的时候咬咬牙,强者站在高处看风景,弱者还在为琐事而焦虑。
js基础部分可到我文章专栏去看 ---点击这里
Day22【2022年8月15日】
学习重点: this指向
1.this绑定规则
this绑定有四个规则:
- 默认绑定
- 隐式绑定
- 显示绑定
- new绑定
this 的指向是在调用时决定的,而不是在书写时决定的。这点和闭包恰恰相反。
1.1默认绑定
一般默认绑定为独立函数的调用。
独立的函数调用可以理解成函数没有被绑定到某个对象上进行调用。 this 指向全局对象。在浏览器中,指向 window;在 Node 中,指向 Global。(严格模式下,指向 undefined)。
1.1.1普通的函数被独立的调用
function foo() {
console.log("foo:", this) //window
}
foo()
// foo: Window {0: global, window: Window, self: Window, document: document, name: '', location: Location, …}
1.1.2定义在对象中函数的独立调用
下文举例出实例进行对比,相当于先赋值再调用。
var obj = {
name: "why",
bar: function() {
console.log("bar:", this) // 输出: window
}
}
obj.bar(); //{name: 'why', bar: ƒ} 隐式绑定
//默认绑定
var baz = obj.bar
console.log(baz) //ƒ () {console.log("bar:", this) // 输出: window}
baz()
1.1.3高级函数
var obj = {
name: "why",
bar: function() {
console.log("bar:", this) // 输出: window
}
}
function test(fn) {
fn()
}
test(obj.bar) // window
严格模式下, 独立调用的函数中的this指向的是undefined
1.2.隐式绑定
常见的式某个对象对函数进行调用
实例
function foo() {
console.log("foo函数:", this)
}
var obj = {
bar: foo
}
obj.bar() // foo函数: {bar: ƒ}
1.3.显示绑定
通过call()/apply()/bind()方法直接指定this的绑定对象
1.3.1 call/apply/bind
首先介绍一些call/apply/bind这三个方法用法及其区别。
1.3.1.1 call
使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。 允许为不同的对象分配和调用属于一个对象的函数/方法。可以用call实现继承,写一个方法,然后让另外一个新的对象来继承它(而不是在新对象中再写一次这个方法)。 用法
function.call(指定this指向, arg1, arg2, ...)
-
指定的两个参数this指向(可选)在非严格模式下,如果不指定/null/undefined的话则默认绑定window。严格模式传入null和undefined,指向对应传入的,如果不传参数this就是undefined。
-
第二个默认为参数列表,依次传入(可选)为多个。
返回值
使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。
实例
var obj = {
name: "why"
}
function foo() {
console.log("foo函数:", this)
}
foo(); //window
foo.call(obj); //foo函数: {name: "why"}
1.3.1.2 apply
调用一个具有给定 this 值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。把需要传递给fn的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,但是也相当于给fn一个个的传递。
用法
apply(指定this指向, argsArray)
第一个参数this指向用法跟call完全一样 第二个参数arrArray则传入数组,只有两个参数。
返回值 调用有指定 this 值和参数的函数的结果。
实例
//在函数中使用
function foo() {
console.log("foo函数:", this)
}
var obj = {
name: "why"
}
foo.apply(obj); //foo函数: {name: "why"}
var numbers = [5, 6, 2, 3, 7];
var max = Math.max.apply(null, numbers); // 7
console.log(max);//7
虽然这个函数的语法与 call() 几乎相同,但根本区别在于,call() 接受一个参数列表,而 apply() 接受一个参数的单数组。
1.3.1.3 bind
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
bind并不会把函数立即执行,它是预先处理函数中的this和参数的,语法和call一模一样,区别在于立即执行还是等待执行,bind不兼容IE6~8。bind在实际的运用中式相对较少(react中有较多运用),一般开发过程中用call,apply用的较多。
用法完全跟call一样
返回值
返回一个原函数的拷贝,并拥有指定的 this值和初始参数。
实例 从实例可看出,直接调用bind不执行,回返回该函数,用赋值语句可调用指向bind。,若函数没形惨则传入argurments回接收到。
function foo(name) {
console.log("foo函数:", this);
console.log("打印参数列表",name);
console.log(arguments); //Arguments(2) ['1sefrser', '2sefrser', callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
var obj = {
name: "why"
}
foo.bind(obj); //不调用函数,返回一个函数
foo.call(obj,'1sefrser'); //foo函数: {name: "why"} 打印参数列表 1sefrser
//调用bind
// var foo1 = foo.bind(obj);
// foo1("1sefrser"); //foo函数: {name: "why"} 打印参数列表 1sefrser
var foo1 = foo.bind(obj,'1sefrser','2sefrser');
foo1(); //foo函数: {name: "why"} 打印参数列表 1sefrser
1.4 new绑定
回顾一下new会执行那些操作
使用new关键字来调用函数时,会执行如下的操作:
- 创建一个全新的对象;
- 这个新对象会被执行prototype连接;
- 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
- 如果函数没有返回其他对象,表达式会返回这个新对象。
使用new关键字调用一个构造函数时会默认return函数中的属性值(函数中没有定义return也会返回)。 可看day12 return的介绍有解析。
function foo() {
this.name = "why"//如果没有属性则返回空对象
console.log("foo函数:", this)
}
new foo()
// foo函数: foo {name: 'why'}
//foo {name: 'why'} //默认返回的
2.this指向优先级
先说以上几种this绑定规则的优先级的结论(依次从上到下):
- new
- bind
- apply/call
- 隐式绑定
- 默认绑定
默认规则的优先级最低 毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this。
显示绑定优先级高于隐式绑定
代码测试
function foo() {
console.log("foo:", this)
}
// 1.1.测试一:apply高于默认绑定
var obj = { foo: foo }
obj.foo.apply("abc")
obj.foo.call("abc")
// 1.2.测试二:bind高于默认绑定
var bar = foo.bind("aaa")
var obj = {
name: "why",
baz: bar
}
obj.baz()
new绑定优先级高于隐式绑定
代码测试
function foo() {
console.log("foo:", this)
}
// 2.new绑定优先级高于隐式绑定
var obj = {
name: "why",
foo: function () {
console.log("foo:", this)
console.log("foo:", this === obj)
}
}
new obj.foo()
new绑定优先级高于bind new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高new绑定可以和bind一起使用,new绑定优先级更高,且bind大于call。
代码测试
function foo() {
console.log("foo:", this)
}
// 3.new/显式
// 3.1. new不可以和apply/call一起使用
// 3.2. new优先级高于bind
function foo() {
console.log("foo:", this)
}
var bindFn = foo.bind("aaa")
new bindFn()
// 4.bind/apply优先级
// bind优先级高于apply/call
function foo() {
console.log("foo:", this)
}
var bindFn = foo.bind("aaa")
bindFn.call("bbb")
3.箭头函数的this
箭头函数中的 this 比较特别,它和严格模式、非严格模式啥的都没关系。它和闭包很相似,都是认“死理”—— 认“词法作用域”的家伙。所以说箭头函数中的 this,和你如何调用它无关,由你书写它的位置决定(和咱们普通函数的 this 规则恰恰相反~) 回顾一下箭头函数一些特性:
- 箭头函数不会绑定this、arguments属性;
- 箭头函数不能作为构造函数来使用(不能和new一起来使用,会抛出错误)没有prototype。
箭头函数没有自己的this,说默认绑定外层this式错误的说法,其实箭头函数的this类似于变量查找规则,这样显得箭头函数中有自己的this,本质上式符合变量查找规则。
箭头函数中, 压根没有this
var bar = () => {
console.log("bar:", this)
}
bar() //window
//通过apply调用时, 也是没有this
bar.apply("aaaa") //window
console.log("全局this:", this)
var message = "global message"
查找规则 bar向上查找,到obj的name不是一个作用域,只是一个对象而已,所以还会继续向上查找直到找到window的this。
var obj = {
name: "obj",
foo: () => {
var bar = () => {
console.log("bar:", this)
}
return bar
}
}
var fn = obj.foo()
fn.apply("bbb") //window
4.一些特殊的规则
在三种特殊情境下,this 会 100% 指向 window:
- 立即执行函数(IIFE)
- setTimeout 中传入的函数
- setInterval 中传入的函数
正常显示绑定
var name = 'BigBear'
var me = {
name: 'xiuyan',
hello: function() {
console.log(`你好,我是${this.name}`)
}
}
me.hello() // 你好,我是xiuyan
立即执行函数实例:
实际上是默认绑定,找到挂载在全局作用域下的name
var name = 'BigBear'
var me = {
name: 'xiuyan',
// 声明位置
sayHello: function() {
console.log(`你好,我是${this.name}`)
},
hello: function() {
(function(cb) {
// 调用位置
cb()
})(this.sayHello)
}
}
me.hello() // 你好,我是BigBear
setTimeout 和 setInterval传入的函数
var name = 'BigBear'
var me = {
name: 'xiuyan',
hello: function() {
setTimeout(function() {
console.log(`你好,我是${this.name}`)
})
}
}
me.hello() // 你好,我是BigBear
以上三种特殊情况,用call/apply/bind也无法改变他们的this指向,这点倔强跟箭头函数很相似认死理。
立即执行函数实例
var name = 'BigBear'
var me = {
name: 'xiuyan',
// 声明位置
sayHello: function() {
console.log(`你好,我是${this.name}`)
},
hello: function() {
// (function(cb) {
// // 调用位置
// cb()
// })(this.sayHello)
(function(){
console.log(`你好,我是${this.name}`)
})()
}
}
me.hello() // 你好,我是BigBear
me.hello.call({a:1}) //你好,我是BigBear
me.hello.apply({a:1}) //你好,我是BigBear
me.hello.bind({a:1})() //你好,我是BigBear
setTimeout 和 setInterval实例
var name = 'BigBear'
var me = {
name: 'xiuyan',
hello: function() {
setTimeout(function() {
console.log(`你好,我是${this.name}`)
})
}
}
me.hello() // 你好,我是BigBear
me.hello.call({a:1}) // 你好,我是BigBear
me.hello.apply({a:1}) // 你好,我是BigBear
me.hello.bind({a:1})() // 你好,我是BigBear
5.严格模式
use strict 是一种 ECMAscript5 添加的(严格模式)运行模式,这种模式使得 Javascript 在更严格的条件下运行。设立严格模式的目的如下:
- 消除 Javascript 语法的不合理、不严谨之处,减少怪异行为;
- 消除代码运行的不安全之处,保证代码运行的安全;
- 提高编译器效率,增加运行速度;
- 为未来新版本的 Javascript 做好铺垫。
区别:
- 禁止 this 关键字指向全局对象。
- 对象不能有重名的属性。
- 未声明的变量赋值,则会导致抛出 ReferenceError
- 严格模式不允许使用 with 语句,否则会抛出错误(影响性能)
- 未经声明就初始化变量会报错。 对函数的限制: 严格模式对函数也有一些限制:
- 函数不能以 eval 或 arguments 作为名称;
- 函数的参数不能叫 eval 或 arguments;
- 两个命名参数不能拥有同一个名称。 如果违反上述规则,则会导致语法错误,代码也不会执行。
ES6 模块默认在严格模式下执行。
6.严格模式下一些特殊规则
严格模式下不完全所有的情况调用this都是返回undefined这个要视情况而定,不能形成固式思维
普通函数下this指向
function showThis() {
console.log(this)
}
showThis() // window
'use strict'
//没有指定对象返回undefined
function showThis() {
console.log(this)
}
showThis() // undefined
全局代码中的 this 在严格模式下的表现(全局作用域下执行的函数 / 代码段里的 this)
直接在全局代码里尝试去拿 this
'use strict'
console.log(this) //window
setTimeout 和 setInterval等挂载在window下的方法
var me = {
name: 'xiuyan',
hello: function() {
// 全局作用域下实现的延时函数
setTimeout(function() {
console.log(`你好,我是${this.name}`)
})
}
}
me.hello() // 你好,我是BigBear
像这样处于全局代码中的 this, 不管它是否处于严格模式下,它的 this 都指向 Window(
这点要特别注意,区分度非常高,很多同学面试的时候会误以为这里也是 undefined)
class和bable编译后下为严格模式 严格模式this默认绑定(绑定window)为undefined
5.今日精进
Day23【2022年8月16日】
学习重点: this指向一些面试题及补充
1.题目一
var name = "window";
var person = {
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss = person.sayName;
sss(); // 绑定: 默认绑定, window -> window
person.sayName(); // 绑定: 隐式绑定, person -> person
(person.sayName)(); // 绑定: 隐式绑定, person -> person
(b = person.sayName)(); // 术语: 间接函数引用, window -> window
}
sayName();
2.题目二
var name = 'window'
// {} -> 对象
// {} -> 代码块
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
// console.log(this) // 第一个表达式this -> person1
// console.log(this) // 第二个表达式this -> person2
// console.log(this) // 第三个表达式this -> person1
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
// 开始题目:
person1.foo1(); // 隐式绑定: person1
person1.foo1.call(person2); // 显式绑定: person2
person1.foo2(); // 上层作用域: window
person1.foo2.call(person2); // 上层作用域: window
person1.foo3()(); // 默认绑定: window
person1.foo3.call(person2)(); // 默认绑定: window
person1.foo3().call(person2); // 显式绑定: person2
person1.foo4()(); // person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1
3.题目三
var name = 'window'
/*
1.创建一个空的对象
2.将这个空的对象赋值给this
3.执行函数体中代码
4.将这个新的对象默认返回
*/
function Person(name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
},
this.foo2 = () => console.log(this.name),
this.foo3 = function () {
return function () {
console.log(this.name)
}
},
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
// person1/person都是对象(实例instance)
var person1 = new Person('person1')
var person2 = new Person('person2')
// 面试题目:
person1.foo1() // 隐式绑定: person1
person1.foo1.call(person2) // 显式绑定: person2
person1.foo2() // 上层作用域查找: person1
person1.foo2.call(person2) // 上层作用域查找: person1
person1.foo3()() // 默认绑定: window
person1.foo3.call(person2)() // 默认绑定: window
person1.foo3().call(person2) // 显式绑定: person2
person1.foo4()() // 上层作用域查找: person1(隐式绑定)
person1.foo4.call(person2)() // 上层作用域查找: person2(显式绑定)
person1.foo4().call(person2) // 上层作用域查找: person1(隐式绑定)
4.题目四
var name = 'window'
/*
1.创建一个空的对象
2.将这个空的对象赋值给this
3.执行函数体中代码
4.将这个新的对象默认返回
*/
function Person(name) {
this.name = name
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name)
}
},
foo2: function () {
return () => {
console.log(this.name)
}
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.obj.foo1()() // 默认绑定: window
person1.obj.foo1.call(person2)() // 默认绑定: window
person1.obj.foo1().call(person2) // 显式绑定: person2
person1.obj.foo2()() // 上层作用域查找: obj(隐式绑定)
person1.obj.foo2.call(person2)() // 上层作用域查找: person2(显式绑定)
person1.obj.foo2().call(person2) // 上层作用域查找: obj(隐式绑定)
5.面试题五
// {} -> 对象
// {} -> 代码块
var a = 1
var obj = {
a: 2,
func2: () => {
console.log(this.a)
},
func3: function() {
console.log(this.a)
}
}
// func1
var func1 = () => {
console.log(this.a)
}
// func2
var func2 = obj.func2
// func3
var func3 = obj.func3
func1() // 1
func2() ///1
func3() //1
obj.func2() // 1
obj.func3() //2 不是箭头函数,是普通函数,所以this指向的是obj 显示调用
6.今日精进
接触了产品运营及解决方案架构层面,打开了自身认知,改变了之前的纯想靠技术吃饭,一技术到头的思维。技术只是基石,吃香只是短期只能保你一时,要想持续保持竞争力,关键是跳出技术寻求突破,上升到产品、管理、销售、商业层。程序员带来的商业价值才是其存在的意义,一旦失去商业价值未来随时可被替代。所以技术不是核心,关键是技术背后帮助企业实现的商业之道。
Day24【2022年8月17日】-Day28【2022年8月21日】
外出
精进
利他,就是最好的利己。利他的前提是先做好自己,有余力了再利他。利他类似一种投资行为,很难保证所有投资都有回报,但有时候一个不经意的小项目也许会给带来巨大的收益。
参考资料
- JavaScript高级程序设计(第4版)
- MDN
- 某知名大神的资料
结语
志同道合的小伙伴可以加我,一起交流进步,我们坚持每日精进(互相监督思考学习,如果坚持不下来我可以监督你)。另外共享学习资料的小伙伴也可以联系我,进群技术交流资料free共享,我们交换资料一起努力鸭! ——>点击这里
备注
按照时间顺序倒叙排列,完结后按时间顺序正序排列方便查看知识点,工作日更新。