call/apply: 改变 this 指向
- call 和 apply 的作用是 改变 this 指向
区别就是传参列表不同 -
Call 的两种用法: 任何一个方法都可以 .call()
-
执行函数
function test(){}
test();//执行函数
test.call();//执行函数的本来面目,和上面 test(); 是一模一样的 -
改变函数里 this 指向
function Person(name, age){
this.name = name;
this.age = age;
}
var person = new Person('deng', 10);
var obj = {}
Person.call(obj, 'cheng', 300); //这个 call 改变得东西就多了- 它把 Person 里的 this 指向了 obj
- 注意: Person 里的 this 在没有一个对象来 new 的时候,是指向 Window 也就是说 Person 只当一个普通方法来调用的时候 this 是没有固定指向的
当新建了一个对象的时候 var person = new Person('deng', 10); 对于这个具体的对象来说 this 指向了它自己,隐性生成 this{__prpto__:Person.prototype} - 后面的(, 'cheng', 300) 是传参, 此时输出 obj 结果为 {name:cheng, age: 300}
- Person.call(obj)这个方法相当于 把 obj 传到了 Person 方法里了,方法变成了
function Person(name, age){
//this == obj
obj.name = name;
obj.age = age;
} - 相当于 obj 临时借用 Person 的方法,实现了自己的功能
- 它就是在执行函数这一次的时候改变了指向,执行完了 Person 里的 this 该指向 window 还指向 window
企业级开发的实际应用,借用别人的函数实现自己的功能
call 改变 this 指向,借用人家的方法的一个实际应用, 比如其他人写了一个类, 你的类和他的一模一样,就是多了一些功能而已,那么你就可以不用重复的去写了,直接 call 借用人家的方法就行了
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name,age,sex,grade,call){
Person.call(this, name, age, sex);//借用人家的方法 让人家方法里的 this 指向我们的 this, 想当与我们的 this 被加上了 name, age, sex 属性方法之类的
this.grade = grade;
this.call = call;
}
var student1 = new Student("Tracy", 10, "girl", 2, "1234"); -
-
Apply
- 和 call 改变 this 指向的同样的作用
- 但是不像 call, call 了之后实参可以一个一个传进去, apply 只能传一个实参,并且这个实参必须是数组的形式
function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name,age,sex,grade,call){
Person.apply(this, [name, age, sex]);//注意这里后边的一个参数是一个数组,和 call 的唯一区别
this.grade = grade;
this.call = call;
}
var student1 = new Student("Tracy", 10, "girl", 2, "1234"); -
This 4 条知识点
- 函数预编译过程 this -> windows (GO 对象)
func() 里的 this 指向 window; obj.func() 里的 this 指向 obj
function test (c)
{
var a = 123;
function b(){}
}
test(1);
预编译
AO{
this:window,
a:undefined;
c:1;
arguments : [1];//注意,我们函数里的参数 arguments 预编译的时候,也就存在了
b:function(){}
}
情形 2
function test (c)
{
//new 的时候 隐式改变 this 指向,但是预编译的时候还仍然指向 window
//var this = Object.creat(test.prototype);
//相当于 this = {__proto__:test.prototype}
var a = 123;
function b(){}
//return this;
}
var obj = new test(1);
预编译
AO{
this:window,
a:undefined;
c:1;
arguments : [1];//注意,我们函数里的参数 arguments 预编译的时候,也就存在了
b:function(){}
} - 全局作用域里 this -> windows
控制台 直接打印 this 显示出 window 对象 - call/apply 可以改变"函数运行时" this 指向
- obj.func(); 里面的 this 指向 obj;
func() 里的 this 指向 window 因为没有人调用这个方法的时候相当于 window.func();
aa.action(); // action 方法里面的this : 谁调用的这个方法,this 就指向谁
因此 Demo:
Person.prototype = {
name : "a",
sayName : function(){console.log(this.name);}
}
function Person(){
this.name = "b";
}
var person1 = new Person();
person1.sayName(); //值为 b, this 值向的是 person1 - 经典题
var name = "222"; var a = { name : "111", say: function(){ console.log(this.name); } } var fun = a.say; //相当于函数体,并没有执行函数 fun(); // 222 相当于把 function say() 直接拿到全局来执行, 没有对象调用 say, 此时 this 指向 windows; a.say(); //111 这时是 a 对象在调用 say(); var b = { name:"333", say: function(fun){ fun(); } } b.say(a.say); //222,b.say 调用这个方法的时候 b 里的 this 确实指向了 b //但是, 注意了 b.say() 里面调用的 是 fun(),这个 fun() 是走的全局的预编译环节,因为没有 对象调用这个 fun(), 比如说 obj.fun() b.say = a.say; b.say(); //333, this 仍然指向 b
var obj = { info:"abc", action: function(){ var self = this; console.log("outside " + this.info); console.log("outside " + self.info); (function(){ console.log("inside this " + this.info); console.log("inside self " + self.info); })(); } } obj.action(); //最终结果 outside abc //action 函数有对象来调用,因此它里面的 this 是指向对象 obj 的 outside abc //自己的 AO 对象里有 self 值 inside this undefined //立即执行函数 因为没有对象调用,所以 this 是指向 windows 的,走的是预编译路线 inside self abc //立即执行函数里的 self 在自己本身的 AO 里找不到值,就会去上一层 action 里的 AO 找,因此它有值
- 函数预编译过程 this -> windows (GO 对象)
继承模式
- 传统模式 : 原型链 prototype
缺点: 过多的继承了没有用的属性Grand.prototype.lastName = "Deng";
function Grand(){}
var grand1 = new Grand();
Father.prototype = grand1;
function Father(){}
var father1 = new Father();
Son.prototype = father1;
function Son(){}
var son1 = new Son(); // 这个 son1 可以有它上面的链儿上的所有属性, 原型链的链接点就是 prototype - 借用构造函数 Call, Apply
缺点: 只能用人家构造函数里已存在的方法,不能继承借用够着函数的原型
每次构造函数都要多走一个函数
这个继承,只限定在别人的函数完全涵盖你的方法,并且你只需要比别人多一些方法或属性而已function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
function Student(name,age,sex,grade,call){
Person.apply(this, [name, age, sex]); //注意这里后边的一个参数是一个数组,和 call 的唯一区别
Person.call(this, name, age, sex);
this.grade = grade;
this.call = call;
}
var student1 = new Student("Tracy", 10, "girl", 2, "1234"); - 共享原型
缺点: 不能随便改动自己的原型
Father.prototype.lastName = "Deng"
function Father(){}
Son.prototype = Father.prototype; //此时 Father 和 Son 共享一个原型,但是这样 Son 想给自己的原型加东西, Father 也会跟着变,因为对象指向了同一个地方。
function Son(){}
var son1 = new Son();
// 这个 son1 可以有 Father.prototype 所有属性, 但是又不继承 Father 里自带的属性方法 -
共享原型 圣杯模式 重点,背下来
它现在就是继承的一个标准模式了,
那么为了解决共享原型出现的弊端,想要Son 继承 Father 的原型,但是Son 自己的原型还需要有自己独特的属性和方法Father.prototype.lastName = "Deng" function Father(){} function Son(){} function inherit(Target, Origin){ function Middle(); //加了这么一个中间商 Middle.prototype = Origin.prototype; Target.prototype = new Middle(); Target.prototype.constructor = Target; //把Target的构造函数指向归位 Target.prototype.uber= Origin.prototype; //为了让我们知道Target真正继承自谁 } inherit(Son, Father); var son1 = new Son();
- Target.prototype = new Middle(); //相当于 var middle = new Middle(); Target.prototype = middle; 这样 Son 的原型,只是和中间商的一个对象 middle 发生了联系,而并不会影响 Father.prototype.
- Target.prototype.constructor = Target; //把Target的构造函数指向归位, 让 Son 的 constuctor 指向正确的 自己,否则它会根据上一条语句而指向 middle.constructor 进而指向 Father.prototype.constructor 这条也不是必须的,就是为了继承的更加完美
- Target.prototype.uber= Origin.prototype; //为了让我们知道Target真正继承自谁, 这个可写可不写,这个只是以防将来调用,如果有一天你想知道自己真正继承自哪个函数,就自定义了一个 uber 的属性来方便调用
- var son1 = new Son(); // 这个 son1 可以有 Father.prototype 所有属性, 但是又不继承 Father 里自带的属性方法; Son.prototype.sex = "male"; //这样随便给 Son 加属性也不会影响其他人
圣杯模式更专业的写法: 立即实行函数,将中间函数 F 在闭包里变量私有化var inherit = (function(){ var F =function (){} ; return function(Target,Origin){ F.prototype = Origin.prototype; Target.prototype = new F(); Target.prototype.constructor = Target; Target.prototype.uber = Origin.prototype; } }()) inherit(Son,Father);//调用
命名空间
- 解决 JS 命名冲突
- 利用闭包进行 模块化开发,防止污染全局变量
公司合作开发常用var name = "abc"; var initLiu = (function(){ //最开始的入口函数,初始化的函数,我们叫作 init var name = "bcd"; //这里的 name 是私有的,不会污染全局变量,并且外部调用不到它 function callName(){####} return fuction(){ //这个函数被保存到了外面 callName(); } }()); var initDeng = (function(){ var name = "bcd"; function callName(){####} return fuction(){ callName(); } }());
对象枚举
- 链式调用 模仿 jQuery
function deng = { smoke: function() { console.log('smoking'); // 如果不加return 默认 return undefined return this; }, drink: function() { console.log('drinking'); return this; } } deng.smoke().drink().smoke();
- 属性拼接
var obj = { name1: 'xiaozhang', name2: 'xiaoliu', name3: 'xiaowang' sayName: function(num) { return this['name' + num]; } } obj.sayName(1); // 'xiaozhang' //利用的是 obj.name1 == obj['name1']
-
枚举: 遍历 hasOwnProperty instanceof
hasOwnProperty 判断该值是否自己的属性或者方法,不要原型中的属性和方法 经常和 for in 搭配
判断数据类型
aa 是不是数组, aa 是不是对象,返回 boolen 值
aa instanceof Array
aa instanceof Object// 1. for var arr = [1, 2, 3, 4, 5, 6]; for(var i = 0; i < arr.length; i++) { console.log(arr[i]); } // 2. for in // 默认会访问到原型 系统自带的原型不会打印出来 手动设置的会打印出来 var obj = { name: 'xiaozhang', age: 18, sex: 'male', height: 170, weight: 75, prop: 1, __proto__: { //for in 只会打印这里我们自己手动设置的原型,系统自带的不会打印的 lastName: 'wang' } } obj.prototype.abc = 123; //这里也三手动自己设置的原型 for(var prop in obj) { //for in 循环,专门遍历对象属性的 console.log(prop + ' ' + typeof prop); console.log(obj[prop]); // console.log(obj.prop); 这么写输出 undefined,因为它相当于 obj['prop'] 访问的是 obj 里的 prop 属性 } // hasOwnProperty 判断该值是否自己的属性或者方法,不要原型中的属性和方法 for(var prop in obj) { if(obj.hasOwnProperty(prop)) { // filter console.log(obj[prop]); // 不会打印出原型的属性 } ] // 3. in 运算符 // 返回 Boolean 判断能不能找到该属性 一般不用,就算是你原型上的属性,也认为你自己也有该属性 var obj = { name: 'zhangsan', age: 20 } console.log('name' in obj); // true, 注意 name 外边要有引号 // instanceof 操作符 // A instanceof B // 它的本意是看 A 对象是不是 B 构造函数构造出来的 很常用 // 根本的意思是: 看 A 对象的原型链上 有没有 B 的原型 function Person() {} var obj = {} var person = new Person(); console.log(person instanceof Person); // true console.log(person instanceof Object); // true console.log(obj instanceof Person ); // false console.log([] instanceof Object); // true console.log([] instanceof Array); // true 空数组是数组构造出来的
- Demo 查看传来的一个变量是数组还是对象
var obj = "外部会传来一个值,也许是数组,也许是对象" // {} || [] function checkObj(value) { if (value instanceof Array) { return "数据是数组";} else if (value instanceof Object) { return "数据是对象";} return "数据既不是数组也不是对象"; //也可以 利用 toString 方法 // Object.prototype.toString.call(value); }
具体 this 指向可以查看 coderwhy 老师的文章: https://mp.weixin.qq.com/s/hYm0JgBI25grNG_2sCRlTA