ECMA基础(四)
this
this是JavaScript的关键字,是当前环境执行期上下文的一个属性,在不同的环境中node/window下表现是不同的
全局对象
- 全局作用域下的
this是全局对象this === window web的全局对象有:window,self,framesnode的全局对象有:globalworker的全局对象有:selfglobalThis可以拿到不同环境下的全局对象
严格模式
函数内部的this指向undefined
指向
- 函数内部还没实例化的
this指向window - 全局范围的
this指向window - 预编译函数
this指向window apply/call改变this指向- 构造函数的
this指向实例化对象
普通函数内部的this
//this//函数内部的this//函数内部还没实例化的this指向windowfunction test(b) {this.d = 3; //window.d = 3var a = 1;function c() {}}test(123);console.log(d); //3console.log(this.d); //3console.log(window.d); //3/*** A0 = {* arguments: [123],* this: window,* b: undefined -> 123,* a: undefined,* c: function c(){}* }*/
构造函数的this
//this指向实例化对象function Test() {//var this = {// __proto__: Test.prototype//}this.name = '123';}var test = new Test();//关于构造函数的AO/GO/*** new之前:* AO = {* this: window* }** GO = {* Test: function Test(){...}* }//*** new之后:* AO = {* this: {* name: '123',* __proto__: Test.prototype* }** }** GO = {* Test: function Test(){...},* test: {* name: '123',* __proto__: Test.prototype* }* }*/
call()/apply()的this
//call / applyfunction Person() {this.name = 'zhangsan';this.age = 18;}function Programmer() {//this -> PersonPerson.apply(this);//引申: 如果Person函数有形参,需要传参数// Person.apply(this, [name, age]);this.work = 'Programming';}var p = new Programmer();console.log(p);
this进阶
预编译:
函数执行 -> AO-> 产生this-> this的指向与执行方式有关
- 默认绑定规则
隐式绑定规则: 对象调用(谁调用指向谁)
- 隐式丢失
- 参数赋值
- 显示绑定:
call/apply/bind new绑定
优先级问题:
new显示 > 隐式 > 默认
//1.默认绑定规则//全局this指向windowconsole.log(this === window); //true//函数的独立调用: 函数内部this指向windowfunction test() {console.log(this === window); //true}test();
//2.隐式绑定规则: 对象调用(谁调用指向谁)//隐式丢失,参数赋值的情况,导致隐式绑定失败var a = 0;var obj = {a: 2,foo: function () {console.log(this); //{a: 2, foo: ƒ} //被抛出闭包后,这里this改变为windowfunction test1() {console.log(this); //window}//函数独立调用指向windowtest1();//函数立即执行也算是函数独立调用(function () {console.log(this); //window})();//闭包: 当函数执行时导致函数被定义并抛出function test2() {console.log(this); //window}return test2;}}obj.foo();//调用闭包函数obj.foo()();
//对象调用(谁调用指向谁)的例外//隐式丢失var a = 0;function foo1() {console.log(this);}var obj1 = {a: 2,foo1: foo1}obj1.foo1(); //this -> obj1//当前全局变量bar持有foo1的引用var bar = obj1.foo1;bar(); //this -> window//解读://观察this在哪里执行//obj里执行指向obj//bar()独立调用执行window//当方法被重写/函数赋值时会存在隐式丢失 -> 函数独立调用//var bar = obj1.foo1; -> var bar = foo1; -> 函数独立调用foo1();
//关于调用方式//独立调用的方式obj.foo();//以下三个不同的调用方式bar.call();bar.apply();bar.bind();
//函数的参数赋值的情况//参数赋值也会存在丢失使内部调用函数变为独立调用执行var a = 0;function foo() {console.log(this); //只要独立调用 this -> window}//父函数是有能力决定子函数的this指向的function bar(fn) {//1.bar()执行内部产生this指向//2.fn形参在预编译时有赋值的过程 fn: obj.fooconsole.log(this);//3.foo() -> fn()独立调用执行// fn();//4.强行改变this指向// fn.call(obj); //this -> obj//5.还可以new 来改变this指向// new fn(); //this -> foo//6.还可以这样强行改变this指向// fn.bind(obj)(); //this -> obj}var obj = {a: 2,foo: foo}//obj.foo理解为持有foo引用地址的变量bar(obj.foo);
//高阶函数里面参数的this//api接口中指明的var arr = [1, 2, 3];arr.forEach(function (item, idx, arr) {//这里的this由谁来决定?console.log(this); //window});arr.sort(function (a, b) {console.log(this);return a - b;});var t = setInterval(function () {console.log(this);})clearInterval(t);
//3.显示绑定: call/apply/bindvar obj = {};//call/apply/bind使用和传参//关于call/apply第一个参数默认绑定为window对象//如果绑定原始值会返回包装类obj.foo(1, 2, 3);bar.call(obj, 1, 2, 3);bar.apply(obj, [1, 2, 3]);bar.bind(obj)(1, 2, 3);
//4.new绑定function Person() {// var this = {};// this.a = 1;// //这里的this实际上是函数实例化之后返回的结果// return this;return 1;}//this -> personvar person = new Person();
练习题:
var name = '222';var a = {name: '111',say: function () {console.log(this.name);}}var fun = a.say;fun(); //222a.say(); //111
关于优先级:
//显示绑定vs隐式绑定,谁优先级更高?function foo() {console.log(this.a);}var obj1 = {a: 2,foo: foo}var obj2 = {a: 3,foo: foo}obj1.foo(); //2obj2.foo(); //3//call能更改this的指向,说明显示比隐式绑定优先级更高obj1.foo.call(obj2); //3obj2.foo.call(obj1); //2
//new绑定vs显示绑定,谁优先级更高?//new更高function foo(b) {this.a = b;}var obj1 = {};var bar = foo.bind(obj1);bar(2);console.log(obj1.a); //2var baz = new bar(3);console.log(obj1.a); //2console.log(baz.a); //3
//关于箭头函数function foo() {console.log(this); //obj -> obj.foo()//子函数默认独立调用指向windowfunction test() {console.log(this); //this}test();//此时可以利用箭头函数让this指向objvar test2 = () => {//箭头函数内部默认没有this,而是直接拿父函数的this(外层函数的作用域的this)console.log(this); //obj}test2();}var obj = {a: 1,foo: foo}obj.foo();
全部绑定规则对箭头函数更改this的方法不适用,箭头函数中的this取决于父函数中this的指向
//尝试更改箭头函数的this指向function foo() {console.log(this);var test = () => {console.log(this);}return test;}var obj1 = {a: 1,foo: foo}var obj2 = {a: 2,foo: foo}var obj3 = {a: 2,foo: () => {console.log(this);}}//默认绑定规则(独立调用 对箭头函数无效)obj1.foo()(); //this -> obj//隐式绑定规则(对象调用 对箭头函数无效)obj3.foo(); //this -> window//显示绑定规则(对箭头函数无效)//foo()执行完返回test, test.call(obj2)//如果call生效指向obj2var bar = foo().call(obj2); //this -> window//new绑定规则(对箭头函数无效) 不能实例箭头函数var foo = () => {console.log(this);}new foo(); //报错 没有constructor
应用场景:
例子1
var name = 'window';var obj1 = {name: '1',fn1: function () {console.log(this.name);},fn2: () => console.log(this.name),fn3: function () {return function () {console.log(this.name);}},fn4: function () {return () => console.log(this.name);}}var obj2 = {name: '2'};//对象调用 谁调用指向谁obj1.fn1(); //this -> obj1//改变调用对象obj1.fn1.call(obj2); //this -> obj2//对象的箭头函数内部不存在this找父作用域的thisobj1.fn2(); //this -> window//箭头函数不适用显示调用规则 无效 this原本是什么就是什么obj1.fn2.call(obj2); //this -> window//独立调用 指向windowobj1.fn3()(); //this -> window//显形规则生效 改变指向obj1.fn3().call(obj2); //this -> obj2//外层改变指向,但是内层自己独立调用 所以指向windowobj1.fn3.call(obj2)(); //this -> window//箭头函数内部不存在this找父作用域fn4,而fn4是通过对象obj1调用obj1.fn4()(); //this -> obj1//箭头函数内部不存在this找父作用域fn4,而fn4是通过对象obj1调用//这里call改变的是箭头函数指向但不生效obj1.fn4().call(obj2); //this -> obj1//箭头函数内部不存在this找父作用域fn4,而fn4是通过对象obj1调用但fn4被改变指向为obj2//这里call改变的是fn4指向生效obj1.fn4.call(obj2)(); //this -> obj2
构造函数
自定义构造函数
作用:
- 模块化
- 插件化
- 组件化
写法:
- 大驼峰(区分普通函数)
//没执行之前,this不存在function Teacher() {this.name = 'zhangsan';this.sex = 'male';this.smoke = function () {console.log('I am smoking');}}//实例化之后,this执行指向实例对象var teacher1 = new Teacher();var teacher2 = new Teacher();teacher1.name = 'lisi';console.log(teacher1, teacher2);
function Teacher() {this.name = 'zhangsan';this.sex = 'male';this.weight = 130;this.smoke = function () {this.weight--;console.log(this.weight);}this.eat = function () {this.weight++;console.log(this.weight);}}var t1 = new Teacher();var t2 = new Teacher();t1.smoke(); //130t1.smoke(); //129console.log(t2.weight); //130
传参:
function Teacher(name, sex, weight, course) {this.name = name;this.sex = sex;this.weight = weight;this.course = course;this.smoke = function () {this.weight--;console.log(this.weight);}this.eat = function () {this.weight++;console.log(this.weight);}}var t1 = new Teacher('zhangsan', 'female', 145, 'JavaScript');var t2 = new Teacher('lisi', 'male', 98, 'HTML');console.log(t1);//Teacher {name: "zhangsan", sex: "female", weight: 145, course: "JavaScript", smoke: ƒ, …}console.log(t2);//Teacher {name: "lisi", sex: "male", weight: 98, course: "HTML", smoke: ƒ, …}
配置选项化:
function Teacher(opt) {this.name = opt.name;this.sex = opt.sex;this.weight = opt.weight;this.course = opt.course;this.smoke = function () {this.weight--;console.log(this.weight);}this.eat = function () {this.weight++;console.log(this.weight);}}var t1 = new Teacher({name: 'zhangsan',sex: 'female',weight: 145,course: 'JavaScript'});var t2 = new Teacher({name: 'lisi',sex: 'male',weight: 98,course: 'HTML'});console.log(t1);//Teacher {name: "zhangsan", sex: "female", weight: 145, course: "JavaScript", smoke: ƒ, …}console.log(t2);//Teacher {name: "lisi", sex: "male", weight: 98, course: "HTML", smoke: ƒ, …}
实例化原理
一旦执行构造函数,this就会存在,并指向window
function Car() {this.color = 'red';//=> window.color = 'red';}Car();
一旦实例化构造函数this指向实例对象
function Car(color, brand) {this.color = color;this.brand = brand;}var car1 = new Car('red', 'Benz');var car2 = new Car('black', 'Mazda');console.log(car1.color); //redconsole.log(car2.color); //black
构造函数的this
构造实例化对象相当于普通函数执行:
- 页面加载生成
GO - 函数执行生成
AO, 默认存了this对象 - 当new的时候,走完构造函数内部的代码
- 隐式的在构造函数内部底下加入
return this; this指向被赋值的变量并存入GO
/*** 页面加载:* GO = {* Car: function* }* 函数Car执行:* AO = {* this: {}* }* 跑代码:* AO = {* this: {* color: color,* brand: brand* }* }* 将new出来的对象赋值给car1变量* 构造函数Car内部底下加入return this* this指向变量car1并存入GO* GO ={* Car: function,* car1: {* color: 'red',* brand: 'Benz'* }* }*/function Car(color, brand) {this.color = color;this.brand = brand;//return this;//this => car1}var car1 = new Car('red', 'Benz');console.log(car1.color);
自己写一个new过程:
function Car(color, brand) {var me = {};me.color = color;me.brand = brand;return me;}var car = Car('red', 'Mazda');console.log(car.color);console.log(car.brand);
试图强行修改return返回的this(默认return this)
function Car() {this.color = 'red';this.brand = 'Benz';// return 123; //red// return 'string'; //red// return {}; //undefined// return []; //undefined// return function () { }; //undefinedreturn function test() {console.log(1);}; //1}var car = new Car();console.log(car.color);console.log(car);
以上发现当return引用值的时候可以修改return结果,原始值则不能
bind/call/apply
function test() {console.log('a');}//系统隐式调用call()test.call(); //a
call()和apply()的作用:
- 改变
this的指向
写法:
被借用方法的函数名.call/apply(目标函数内部/this, 参数)
function Car(brand, color) {this.brand = brand;this.color = color;this.run = function () {console.log('running');}}var newCar = {displacement: '2.5'};var newCar2 = {};//实现newCar有Car里所有的属性跟方法//Car.call(对象, 参数1, 参数2, ...)Car.call(newCar, 'benz', 'red');//Car.apply(对象, []) //argumentsCar.apply(newCar2, ['benz', 'red']);console.log(newCar);//{displacement: "2.5", brand: "benz", color: "red", run: ƒ}console.log(newCar2);//{brand: "benz", color: "red"}//call()/apply()还不会影响已有属性和方法var car = new Car('mazda', 'grey');console.log(car);//Car {brand: "mazda", color: "grey"}
案例:计算器(借用方法)
多人协作
function Compute() {this.plus = function (a, b) {console.log(a + b);}this.minus = function (a, b) {console.log(a - b);}}function FullCompute() {Compute.apply(this);this.mul = function (a, b) {console.log(a * b);}this.div = function (a, b) {console.log(a / b);}}//借用Compute里面的方法var compute = new FullCompute();compute.plus(1, 2); //3compute.minus(1, 2); //-1compute.mul(1, 2); //2compute.div(1, 2); //0.5
bind和call区别:
bind改变this指向后返回一个新的函数不执行call/apply改变this指向并立即执行
p1.play.call(p2,'男',20);p1.play.apply(p2,['男',20]);p1.play.bind(p2,'男',20)();//类似写法var fn = p1.play.bind(p2,'男',20);fn();
bind重写:
//bind特性://1.不执行//2.实例化时失效var p = {age: 18}function Person() {console.log(this);console.log(this.age);}//函数内部this默认指向window// Person(); window undefined//改变this指向为p对象// Person.call(p); {age: 18} 18// Person.apply(p); {age: 18} 18// Person.bind(p)(); {age: 18} 18//p对象并不是构造函数所以报错// var person = Person.call(p);// new person(); Uncaught TypeError: person is not a constructor//因为bind返回的是一个未执行的函数//所以实例化后bind失效了(new会生成自己的this)所以是undefined// var person = Person.bind(p);// new person();
//重写bindvar p = {age: 18}function Person(name1, name2) {console.log(this);console.log(this.age);console.log(name1, name2);}//更改this指向原理:其实是更改执行期上下文 contextFunction.prototype.myBind = function (context) {//2.但是函数内部this指向window,所以_self保存内部this指向var _self = this,//arguments -> context//从第1位(忽略第0位arguments)开始复制参数//返回一个数组 -> ['andy','lucy']args = Array.prototype.slice.call(arguments, 1),//利用圣杯模式解决同一引用被修改时影响结果temFn = function () {};/**console.log(_self);* ƒ Person() {* console.log(this);* console.log(this.age);* }*///3.传参问题://如何实现两种写法?//写法一://绑定时一起传参//Person.bind(p, 'andy');//写法二://执行时才传参//var p = Person.bind(p, 'andy');//p('andy');//1.因为不执行,所以返回一个新的函数出去return function () {//找到该函数里所有的实参数组列表//newArgs 是空的数组var newArgs = Array.prototype.slice.call(arguments);// console.log(args, newArgs);// ['andy','lucy'] []// console.log(this, _self);//this -> 实例化的对象//_self -> 构造函数Person函数本身//参数1:执行期上下文传入//参数2:把新旧参数拼接一起,追加新传入的数组元素//如何实现实例化后bind失效的问题?//实现方法:为了使实例化对象this是构造函数构造出来的//原理:将实例化后的原型引用直接赋值给fn函数原型,并判断即可var fn = function () {//判断:实例化对象this是否构造函数构造出来的//那么:是的话实例化对象就为实例化对象,不是就为context上下文_self.apply(this instanceof _self ? this : context, args.concat(newArgs));}temFn.prototype = this.prototype;fn.prototype = new temFn();return fn;}}// Person.myBind()(); window undefined// Person.myBind(p)(); {age: 18} 18// var p = Person.bind(p, 'andy', 'lucy')(); andy lucy
链式调用
实现链式调用
var sched = {wakeup: function () {console.log('Running');//this -> schedreturn this;},morning: function () {console.log('Going shopping');return this;},noon: function () {console.log('Having a rest');return this;},afternoon: function () {console.log('Studying');return this;},evening: function () {console.log('walking');return this;},night: function () {console.log('Sleeping');return this;}}sched.wakeup().morning().noon().afternoon().evening().night()
generator&iterator
7种数组遍历的方法:
forEach()普通的数组遍历方法formap()映射 -> 每一次遍历,返回一个数组元素 -> 返回一个新的数组filter()过滤 -> 每一次遍历返回布尔值来决定当前元素是否纳入新的数组中reduce()归纳 -> 每一次遍历将当前元素收归到容器中reduceRight()->reduce的反向操作every()-> 判定是否所有元素都符合条件some()-> 是否有某一个或多个符合一个条件
遍历底层实现:
for循环,遍历就是一次性对数组中每一个元素进行查询和处理
希望遍历的过程是可以控制的(遍历的过程可停止,也可继续),手动的控制遍历流程,这种方式就叫做迭代的过程。
产品迭代 -> 人为控制的产品升级与扩展 -> munally control
生成器和迭代器:
- 生成器是一个函数
- 迭代器是由生成器函数执行后返回的一个带有next方法的对象
- 生成器对迭代的控制是由yield关键字来执行的
写法一:
function* generator() {//第一次遍历yield '姓名: 大田';yield '年龄: 30';yield '爱好: 旅游';return '我爱JavaScript';}//执行const iterator = generator();console.log(iterator.next()); //{value: "姓名: 大田", done: false}console.log(iterator.next()); //{value: "年龄: 30", done: false}console.log(iterator.next()); //{value: "爱好: 旅游", done: false}console.log(iterator.next()); //{value: "我爱JavaScript", done: true}
执行next()方法生成一个对象就是第一个yeild出来的结果
每一次yeild都产出一个迭代的一个对象,迭代对象包含value和done属性 遍历过程结束
写法二:
const arr = ['姓名: 大田', '年龄: 30', '爱好: 旅游'];//生成器函数function* gen(arr) {for (var i = 0; i < arr.length; i++) {yield arr[i];}return '我爱JavaScript';}//执行const iterator = gen(arr);console.log(iterator.next()); //{value: "姓名: 大田", done: false}console.log(iterator.next()); //{value: "年龄: 30", done: false}console.log(iterator.next()); //{value: "爱好: 旅游", done: false}console.log(iterator.next()); //{value: "我爱JavaScript", done: true}
迭代会遍历的区别是:
迭代会把每次的遍历都拆分出来,着就是迭代的过程
写一个迭代函数
function gen(arr) {var nextIndex = 0;return {next: function () {//正常迭代 or 迭代完成//arr[nextIndex ++] 先取值后加1return nextIndex < arr.length ? {value: arr[nextIndex++],done: false} : {value: arr[nextIndex++],done: true}}}}
Arguments对象
函数内部对应参数值的实参列表,也是一个对象,内置的局部变量,本质是类数组对象。
类数组Array-like
- 具有
length属性 - 具有从零开始的属性下标
- 没有数组的内置方法
- 非箭头函数的其他函数的内置的局部变量
越到ES6,arguments越来越弱化
function test(a, b, c) {/*** callee: 宿主函数 test* Sybal(Symbol.iterator): 可迭代对象标识*/// //obj为不可迭代对象// var obj = {// a: 1,// b: 2,// c: 3// }// var it = generator(obj);// console.log(it.next()); //报错不是迭代对象console.log(arguments);console.log(arguments.toString()); //[object Arguments]//证明arguments不是数组console.log(Array.isArray(arguments)); //falseconsole.log(arguments.callee);}test(1, 2, 3);
可迭代对象
//写一个生成器函数function* generator(args) {for (var v of args) {//产出vyield v;}}var it = generator(arguments);console.log(it.next()); //{value: 1, done: false}
非箭头函数的其他函数的内置的局部变量
var test = () => {console.log(arguments);}test();//Uncaught ReferenceError: arguments is not defined//箭头函数把arguments抛弃了//取而代之的是...argsvar test2 = (...args) => {console.log(args);}test2(1, 2, 3); //[1, 2, 3]
arguments泄漏:将arguments类数组变为数组
//将arguments类数组变为数组function test() {// slice()返回一个新的数组var argArr = [].slice.call(arguments);console.log(argArr);}test(1, 2, 3); //[1, 2, 3]
形参实参的对应关系
//形参实参的对应关系function test(a) {/*** 形式参默认情况下是会有共享关系*/// arguments[0] = 10;// console.log(a); //10// console.log(arguments[0]); //10a = 100;console.log();console.log(a); //100console.log(arguments[0]); //100}test(1);//形参种但凡有一个参数有默认值,arguments都不会对应跟踪参数最终的值function test2(a = 100) {arguments[0] = 10;console.log(a); //1console.log(arguments[0]); //10}test2(1);
