this的绑定-严格模式-箭头函数
先知道一些基础的知识顺序掌握
1.严格模式
1.认识严格模式
-
JavaScript历史的局限性:
- 长久以来,JavaScript 不断向前发展且并未带来任何兼容性问题;
- 新的特性被加入,旧的功能也没有改变,这么做有利于兼容旧代码;
- 但缺点是JavaScript 创造者的任何错误或不完善的决定也将永远被保留在 JavaScript 语言中;
-
在ECMAScript5标准中,JavaScript提出了严格模式的概念(Strict Mode);
- 严格模式很好理解,是一种具有限制性的JavaScript模式,从而使代码隐式的脱离了 “ 懒散(sloppy)模式”
- 支持严格模式的浏览器在检测到代码中有严格模式时,会以更加严格的方式对代码进行检测和执行;
-
严格模式对正常的JavaScript语义进行了一些限制:
- 严格模式通过 抛出错误 来消除一些原有的 静默 (silent) 错误
- 严格模式让JS引擎在执行代码时可以进行更多的优化 (不需要对一些特殊的语法进行处理)
- 严格模式禁用了在ECMAScript未来版本中可能会定义的一些语法;
2.开启严格模式
-
那么如何开启严格模式呢?严格模式支持粒度话的迁移:
- 可以支持在is文件中开启严格模式
- 也支持对某一个函数开启严格模式
- 严格模式通过在文件或者函数开头使用use strict 来开启
// 给整个script开启严格模式
"use strict"
//给一个函数开启严格模式
function fun() {
"use strict"
}
-
没有类似于“no use strict”这样的指令可以使程序返回默认模式
- 现代JavaScript支持“class”和“module”,它们会自动启用use strict;
3.严格模式限制
- JavaScript被设计为新手开发者更容易上手,所以有时候本来错误语法,被认为也是可以正常被解析的;
- 但是这种方式可能给带来留下来安全隐患
- 在严格模式下,这种失误就会被当做错误,以便可以快速的发现和修正
- 无法意外的创建全局变量
- 严格模式会使引起静默失败(silently fail,注:不报错也没有任何效果)的赋值操作抛出异常
- 严格模式下试图删除不可删除的属性
- 严格模式不允许函数参数有相同的名称
- 不允许0的八进制语法
- 在严格模式下,不允许使用with
- 在严格模式下,eval不能为上层创建变量
- 严格模式下,this绑定不会默认转成对象
"use strict"
//1.不会意外创建全局变量
// function fun(){
// message="消息"
// }
// fun()
// console.log(message);
//2.发现静默错误
// var obj = {
// name: "林夕"
// }
// Object.defineProperty(obj, "name", {
// writable: false,
// })
// obj.name = "linix";
// console.log(obj.name);//尝试开启或者关闭严格模式观察
// 3.严格模式下试图删除不可删除的属性
// var obj1 = {
// name: "林夕"
// }
// Object.defineProperty(obj1, "name", {
// configurable: false
// })
// delete obj1.name;
// console.log(obj1);
// 4.参数名称不能相同
// function fun(num,num){
// }
// 5.不能以0开头
// console.log(0123);
// console.log(0o123);//0o是es6新增的语法可以使用
// // with语句
// let obj={
// name:"林夕"
// }
// with(obj){
// console.log(name);
// }
// eval函数不能为上层创建变量
// eval(`var linxi="林夕"`)
// console.log(linxi);
2.箭头函数(arrow function)
箭头函数是ES6之后增加的一种编写函数的方法,并且它比函数表达式要更加简洁:
- 箭头函数不会绑定this、arguments属性;
- 箭头函数不能作为构造函数来使用 (不能和new一起来使用,会抛出错误);
- 不会绑定this不代表不能使用,下文会提到
1.箭头函数写法
// es6中提供了新的语法来接收多个参数例如:function foo(...arg){}
// es6之后不推荐使用函数来作为构造函数而是使用class
// 1.之前函数定义方式
function fun1() { }
var fun2 = function (arg) {
console.log("函数体代码");
console.log(arg);
// 接收多个参数会被放在arguments
console.log(this, arguments); // 默认含有两个变量
}
// 2.箭头函数完整写法
let fun3 = (arg) => {
console.log("箭头函数的函数体代码");
console.log(arg);
}
// 3.箭头函数练习
// 3.1 forEach
let numArr = [6, 12, 15, 18];
// numArr.forEach(function(item){
// console.log(item);
// })
numArr.forEach((item,index,arr)=>{
console.log(item,index,arr);
})
// 3.2 setTimeout
setTimeout(()=>{
console.log("setTimeout");
},1000)
2.箭头函数简写
let numArr = [6, 12, 15, 18];
//·1.如果箭头函数只有一个参数,那么()可以省略
numArr.forEach(item => {
console.log(item);
})
// 2.如果函数体中只有一行执行代妈,那么{}可以省略
numArr.forEach(item => console.log(item))
// 一行代码中不能带return关键字,如果省略需要带return一起省略
let newArr1 = numArr.filter(item => {
return item > 12
})
// 3.只有一行代码时,这行代码的表达式结果会作为函数的返回值默认返回的
let newArr2 = numArr.filter(item => item > 12)
// 4. 如果默认返回值是一个对象,那么这个对象必须加()
let strFn = () => "21312"
console.log(strFn());
let boolFn = () => true
console.log(boolFn());
let arrFn = () => ["eg1", "eg2"]
console.log(arrFn());
// let objFn = () => {} 因为这种{}为执行体,js引擎不会解析,下文加上()告诉箭头函数默认返回值是一个对象必须这样加!!!
let objFn = () => ({ name: "林夕" })
console.log(objFn());
// 实现所有numArr中大于12平方和
let sum = numArr.filter(item => item > 12).map(item => item * item).reduce((prevValue, item) => prevValue + item, 0)
建议先看下面this的绑定规则然后最后补充箭头函数的this使用
3.箭头函数this的使用
// 箭头函数中,压根没有this
const fun = () => {
// 当前作用域没有this,会去上层作用域(全局)查找
console.log("fun", this);
}
fun()
console.log("全局this", this);
// 验证示例
// 通过apply调用时,也是没有this
fun.apply("林夕")
// this的查找规则
/* var obj = {
name: "林夕",
fun: function () {
// 当前this是obj对象
var arrowFun = () => {
//上层作用域是fun函数
console.log("arrowFun", this);
}
return arrowFun
}
} */
// 改造obj对象的fun为箭头函数测试观察结果
var obj = {
name: "林夕",
fun: () => {//此时调用继续去·上层(全局)查找this指向
var arrowFun = () => {
console.log("arrowFun", this);
}
return arrowFun
}
}
var fn = obj.fun();
fn.apply("linxi")
4.箭头函数中的this应用
//网络请求的工具函数(模拟)
function request(url, callbackFn) {
var results = ["数据1", "数据2", "数据3"]
callbackFn(results)
}
// 实际操作的位置(业务)
var obj = {
names: [],
network: function () {
// 过去式
// var _this = this
// request("/url", function (res) {
// _this.names = [].concat(res)
// })
// 箭头函数语法
request("/url", (res) => {
this.names = [].concat(res)
})
}
}
obj.network()
console.log(obj.names);
3.this的绑定规则
1.this的绑定规则
function fun() {
console.log("fun函数", this);
}
//1.直接调用
fun()
// 2.通过对象调用
var obj = { name: "obj" }
obj.fun = fun;
obj.fun()
图上案例可知
- 函数在调用时,JavaScript会默认给this绑定一个值
- this的绑定和定义的位置 (编写的位置)没有关系
- this的绑定和调用方式以及调用的位置有关系
- this是在运行时被绑定的;
由此可知this绑定规则分为
1.默认绑定
独立函数调用:独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用
//1.普通的函数被独立的调用
function fun() {
console.log("fun函数", this);
}
fun()
// 2.函数定义在对象中,但是独立调用
let obj = {
name: "obj",
fun: function () {
console.log("objfun", this);
}
}
let objFun = obj.fun;
objFun()
// 3. 高阶函数
function funAd(fn) {
fn()
}
funAd(obj.fun)
"use strict"
// 4.严格模式下,独立调用的函数中的this指向的是undefined
//1.普通的函数被独立的调用
function fun() {
console.log("fun函数", this);
}
fun()
// 2.函数定义在对象中,但是独立调用
let obj = {
name: "obj",
fun: function () {
console.log("objfun", this);
}
}
let objFun = obj.fun;
objFun()
// 3. 高阶函数
function funAd(fn) {
fn()
}
funAd(obj.fun)
2.隐式绑定
通过某个对象进行调用:也就是它的调用位置中,是通过某个对象发起的函数调用
// 案例1
function fun() {
console.log("fun函数", this);
}
let obj = { fun: fun }
obj.fun()
// 案例2
function fun2() {
console.log("fun2函数", this);
}
let obj1 = { name: "obj1", fun: fun2 }
let obj2 = { name: "obj12", obj1: obj1 }
obj2.obj1.fun()
// 案例3
function fun3() {
console.log("fun3函数", this);
}
let obj3 = { name: "obj3", fun3: fun3 }
// 将obj3的fun3赋值给funFn
let funFn = obj3.fun3;
funFn()
3.new绑定
JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字
使用new关键字来调用函数是,会执行如下的操作:
- 创建一个全新的对象;
- 这个新对象会被执行prototype连接
- 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成)
- 如果函数没有返回其他对象,表达式会返回这个新对象;
//创建Person
function Person(name) {
console.log(this); // Person {}
this.name = name; // Person {name:"林夕"}
}
var p = new Person("林夕");
console.log(p);
/*
new关键字执行的操作
1.创建新的空对象
2.将this指向这个空对象
3.执行函数体中的代码
4.没有显示返回非空对象时,默认返回这个对象
*/
function fun() {
console.log("fun函数", this);
this.name = "测试名字";//查看时候打印出赋值的name,此行为为浏览器赋值操作方便调试
}
new fun()
4.显式绑定
隐式绑定有一个前提条件:
- 必须在调用的对象内部有一个对函数的引用 (比如一个属性)
- 如果没有这样的引用,在进行调用时,会报找不到该函数的错误
- 正是通过这个引用,间接的将this绑定到了这个对象上;
如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?
JavaScript所有的函数都可以使用call和apply方法
-
第一个参数是相同的,要求传入一个对象;
- 这个对象的作用是什么呢?就是给this准备的
- 在调用这个函数时,会将this绑定到这个传入的对象上
-
后面的参数,apply为数组,call为参数列表;
var obj = {
name: "林夕"
}
function fun() {
console.log("fun函数", this);
}
//执行函数,并且函数中的this 指向obj对象
obj.fun = fun
obj.fun()
// 执行函数并且强制this就是obi对象
// 注意调用函数()不能乱加加()直接调用
// 显式绑定
fun.call(obj)//等价于上面两行绑定
// 可以自定义决定函数调用this指向
// 包装类对象绑定
fun.call(123)
fun.call("linxi")
// 特殊情况指向window,这种没有包装类
fun.call(undefined)
//独立函数执行默认模式下,绑定window对象
//在严格模式下,不绑定全局对象而是undefined
fun()
2.apply/call/bind
函数补充说明apply-call
// apply/call
function fun() {
console.log("fun函数被调用", this);
}
// 调用
fun()
// apply
fun.apply("林夕")
// call
fun.call("linxi")
在使用 apply/call调用时候那我们参数应该怎么传输呢?
// apply/call
function fun(name, age, height) {
console.log("接收参数", name, age, height);
console.log("fun函数被调用", this);
}
// 调用
fun("参数林夕", 18, 180)
// apply
//·第一个参数:绑定this
//第二个参数:传入额外的实参,以数组的形式
fun.apply("林夕", ["参数林夕", 18, 180])
// call
//第一个参数:绑定this
//参数列表:后续的参数以多参数的形式传递,会作为实参
fun.call("linxi", "参数林夕", 18, 180)
因为上面的过程,我们明确的绑定了this指向的对象,所以称之为 显示绑定。
bind
如果我们希望一个函数总是显示的绑定到一个对象上,可以怎么做呢?
- 使用bind方法,bind() 方法创建一个新的绑定函数 (bound function,BF)
- 绑定函数是一个 exotic function object (怪异函数对象,ECMAScript 2015 中的术语)
- 口在 bind()被调用时,这个新函数的 this 被指定为 bind()的第一个参数,而其余参数将作为新函数的参数
function.bind(thisArg[,arg1[,arg2[,...]]])
function fun(name,age,height) {
console.log("参数",name,age,height);
console.log("fun函数", this);
}
var obj = { name: "林夕" }
// 需求:调用foo时,总是绑定到obj对象身上(不希望obj对象身上有函数)
// 1.bind函数基本使用
var fun1 = fun.bind(obj);//本质生成了一个新函数 此时函数已经绑定obj对象了,this已经被绑定完成
fun1()//this->objS
// 怪异,看起来式独立函数,实际上是已经绑定好了!
// 2.bind函数的其他参数
var fun2=fun.bind(obj,"参数林夕")
fun2(18,180)//这里传参数是继续追加 阅读性较差
3.this绑定优先级
学习了四条规则,接下来开发中我们只需要去查找函数的调用应用了哪条规则即可,但是如果一个函数调用位置应用了多条规则,优先级谁更高呢?
-
默认规则的优先级最低
- 毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this
-
显示绑定优先级高于隐式定
-
new绑定优先级高于隐式绑定
-
new绑定优先级高于bind
- new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高
- new绑定可以和bind一起使用,new绑定优先级更高
function fun() {
console.log("fun函数", this);
}
//比较优先级
//·1.显式绑定绑定的优先级高于隐式绑定
//·1.1.测试-:apply高于默认绑定
var obj = { fun: fun }
obj.fun.apply("测试apply");//即有显示绑定又有隐式绑定
obj.fun.call("测试call")
//·1.2.测试二:bind高于默认绑定
var funBind = fun.bind("测试bind");
var obj1 = {
name: "林夕",
funBind: funBind
}
obj1.funBind()
// 2.new绑定优先级高于隐式绑定
var newObj = {
name: "林夕",
fun: function () {
console.log("fun", this);
console.log("fun", this === obj);
}
}
new newObj.fun()
//3.new/显式绑定,优先级
//3.1.new不可以和apply/calL一起使用
//3.2.new优先级高于bind
var bindFun = fun.bind("bind")
new bindFun()
//4.bind/apply优先级
// bind优先级高Fapply/call
var bindFun2 = fun.bind("bind2")
bindFun2.apply("bind/apply")
bindFun2.call("bind/call")
由此得出优先级结论:new->bind->apply/call->隐式绑定->默认绑定
4.绑定之外的情况
-
情况一:如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则:
//情况一:显式绑定null/undefined,那么使用的规则是默认绑定 /* 严格模式注意观察区别: fun.apply("linxi")不会转换包装类型而是直接·给一个字符串 */ // "use strict"//自行观察在严格模式的this绑定查看 function fun() { // 原始类型或者包装类型都有length属性 // console.log("fun函数", this.length); console.log("fun函数", this); console.log("this类型",typeof this); } fun.apply("linxi") fun.apply(null) fun.apply(undefined) -
间接函数引用
创建一个函数的 间接引用,这种情况使用默认绑定规则
// 2.情况二:间接函数引用 // 赋值(obj2.fun =obj1.fun)的结果是fun函数 // fun函数被直接调用,那么是默认绑定 var obj1 = { name: "obj1", fun: function () { console.log("fun", this); } } var obj2 = { name: "obj2" };//一段代码紧接着是{}[]()要记得加; // obj2.fun=obj1.fun // obj2.fun() (obj2.fun = obj1.fun)()
5.this试题小试
测试一
var name = "林夕";
var obj = {
name: "obj",
fun: function () {
console.log(this.name);
}
}
function fun() {
var objFn = obj.fun;
objFn();
obj.fun();
(obj.fun)();
(b = obj.fun)()
}
fun()
测试二
var name = "林夕";
var person1 = {
name: "person1",
fun1: function () {
console.log(this.name);
},
fun2: () => console.log(this.name),
fun3: function () {
return function () {
console.log(this.name);
}
},
fun4: function () {
return () => {
console.log(this.name);
}
}
}
var person2 = { name: "person2" }
person1.fun1();
person1.fun1.call(person2);
person1.fun2();
person1.fun2.call(person2);
person1.fun3()();
person1.fun3.call(person2)();
person1.fun3().call(person2);
person1.fun4()();
person1.fun4.call(person2)();
person1.fun4().call(person2);
测试三
var name = "林夕";
function Person(name) {
this.name = name;
this.fun1 = function () {
console.log(this.name);
},
this.fun2 = () => console.log(this.name),
this.fun3 = function () {
return function () {
console.log(this.name);
}
},
this.fun4 = function () {
return () => {
console.log(this.name);
}
}
}
var person1 = new Person("person1")
var person2 = new Person("person2")
person1.fun1();
person1.fun1.call(person2);
person1.fun2();
person1.fun2.call(person2);
person1.fun3()();
person1.fun3.call(person2)();
person1.fun3().call(person2);
person1.fun4()();
person1.fun4.call(person2)();
person1.fun4().call(person2);
测试四
var name = "林夕";
function Person(name) {
this.name = name
this.obj = {
name:"obj",
fun1: function () {
return function () {
console.log(this.name);
}
},
fun2: function () {
return () => {
console.log(this.name);
}
}
}
}
var person1 = new Person("person1")
var person2 = new Person("person2")
person1.obj.fun1()();
person1.obj.fun1.call(person2)();
person1.obj.fun1().call(person2);
person1.obj.fun2()();
person1.obj.fun2.call(person2)();
person1.obj.fun2().call(person2);
简单的一些介绍分享,欢迎大家评论·留下答案