继 承
原型继承
- 让类B的原型指向类A的实例,那么以后类B的实例既可以调取类A实例的私有属性,也可以调取类A实例的公有属性,那这种继承方式就是原型继承
- 原型继承:继承私有和公有
- 通过改变prototype的指向,使其指向其他实例
- 原型继承:继承私有和公有
function A(){
this.getX = function(){console.log('恭喜发财')}
};
A.prototype.getY = function(){
console.log('真好')
};
function B(){}
B.prototype = new A;
let f = new B;
中间类继承
- arguments类数组,不是一个数组;虽然不是Array的实例,但是我们可以手动把arguments的__proto__指向Array的原型,那这样arguments就可以使用Array原型上的方法了,这就是中间类继承
- 只能继承公有属性
- 通过自己的__proto__指向类的prototype(原型)
- 只能继承公有属性
function fn(){
console.log(arguments instanceof Array)
arguments.__proto__ = Array.prototype;
console.log(arguments.push(23))
console.log(arguments)
}
fn(1,2,3,4,5)
call继承
- call继承:私有属性
- 改变类里的this指向
- 在类B中,调用了类A,并且通过call改变了类A中的this指向,使其指向B的实例;这样类B创建的实例就具有类A的私有属性;这种继承就是call继承;
- call方法在Function的原型上
/*
原型继承:继承私有和公有
中间类继承:公有
call继承:私有属性
*/
function A(){
this.x =10
}
A.prototype.getX = function(){
console.log('万事如意')
}
function B(){
// this->当前实例
/*
类B当做构造函数执行时,此时的this是当前实例,
*/
this.a = 20
A.call(this) // 把函数A当做普通函数执行,并且把A的this指向了类B的实例
}
//call继承让类B继承了类A的私有属性,但是不能使用类A的公有属性
// 类B的所有实例都可以使用类A的私有属性
let f = new B;
console.log(f)
f.getX()
寄生组合继承
- 继承公有和私有
- 创建一个空对象,让空对象的__proto__指向(传第一个参)类A的原型,在把这个空对象赋值给类B的原型
- 使用call继承继承了私有属性,Object.create继承了公有属性,这种继承方式就是寄生组合继承;
- 为了防止修改B的原型时,修改了A的原型,所以使用Object.create的方法
// Object.create(context): // 创建一个空对象,让对象的__proto__指向你传的第一个参数
let obj = {name:3,getX:function(){console.log(11)}}
let o = Object.create(obj)
console.log(o)
- 实例
// Object.create(context): // 创建一个空对象,让对象的__proto__指向你传的第一个参数
// let obj = {
// name: 3,
// getX: function () {
// console.log(11)
// }
// }
// let o = Object.create(obj)
// console.log(o);
// console.log(o.__proto__ === obj) // true
// 创建一个空对象,让空对象的__proto__指向你传递的第一个参数(obj)
function A(){
this.a = 10
}
A.prototype.getX = function(){
console.log('恭喜发财')
}
function B(){
/*
函数B以构造函数的身份运行
那类B中的this指向当前实例
*/
this.x =20;
A.call(this) // 让函数A以普通函数身份运行,而且把函数A中的this指向了类B的实例
// 继承私有属性
}
B.prototype = Object.create(A.prototype);// 继承公有属性;
// 创建一个空对象,让空对象的__proto__指向类A的原型,在把这个空对象赋值给类B的原型
let f = new B;
f.__proto__.getY = function(){
console.log(333)
};
let m = new A;
// m.getY()
console.log(f)
//call继承让类B继承了类A的私有属性,然是不能继承类A的公有属性
// console.log(f)
// f.getX() // 报错
// f.a // 可以取到
Class继承
// ES6中class创造出来的类不能当做普通函数执行
class A {
constructor(q) {
this.x = q;
}
getX() {
console.log(this.x)
}
}
// ES6中的继承
class B extends A {
constructor(name) {
// 子类继承父类,可以不写constructor,但是你要是一旦写了,
那在constructor里第一句话就要写super()
// 你要是不写constructor,那浏览器会默认创建一个constructor(...arg){
// super(...arg)
// }
super(200) // A.call(this, 200) 把父类当做普通函数执行,给方法传递参数,
让方法中的this是子类的实例
this.y = 100;
}
getX() {
console.log(this.y)
}
}
B.prototype = Object.create(A.prototype); // class定义的类不能改原型重定向
let f = new B(100);
console.log(f)
- B.prototype = Object.create(A.prototype);
This
this详解
- 跟函数执行有关系
- 他是js中的关键字,有特殊的特殊意义
- 他就是函数的执行体,谁执行函数this就是谁
- 不能给this直接赋值
- this传的是指针,空间地址
- this是个关键字;在特殊的情景下,this有特殊的意义;this不能用等号对其直接修改
this的几种情况
1.在全局作用于下,this就是window
2.在函数执行时,看执行函数前有没有".",如果有点,那点前面是谁,this就是谁,如果没有点,那this就是window
3.自执行函数里的this是window
4.给元素事件行为绑定方法,方法里的this指向被绑定的元素本身
5.回调函数里的this一般指向window
6.实例的私有属性或者公有属性里的this一般指向当前实例
7.构造函数里的this是当前实例
8.箭头函数没有this,要是在箭头函数里使用this,就看他上一级作用域的this,不能用call更改,不能被new
9.call、apply、bind可以改变this的指向
//2给元素的事件绑定的函数中的this,指向了当前被点击的那个元素
box.onclick = function () {
// this : 对象
console.dir(this === box); //空间地址相同
box.style.color="red";
this.style.color = "red";
this=100;//this 不能放在等号左边
}
box1.onclick = function () {
//this : 对象
console.dir(this);
}
//5 回调函数中的this 一般指向window
setTimeout(function () {
console.log(this);
}, 1000)
var ary = [1, 2, 3, 4];
ary.map(function () {
console.log(this);
})
//回调函数的特殊
//回调函数但是this更改了
function A(){
console.log(this);
}
function B(a){
// a();
var obj ={a:a};
obj.a();
}
B(A);
/* // var let = 3;
// console.log(let)
// console.log(this)
// console.log(this === window)
// window.a = 12;
// console.log(this.a) */
/* // function fn(){
// console.log(this)
// }
// fn()
// var age = 15;
// var obj = {
// age: 13,
// name: function(){
// console.log(this.age)
// }
// }
// obj.name() // this是obj
// var f = obj.name;
// f() // this是window*/
// (function(){
// console.log(this) // window
// })()
// box.onclick = function(){
// console.log(this)
// }
// var ary = [1, 2];
// ary.map((a,b)=>{
// console.log(this) // window
// })
// ary.sort((a,b)=>{
// console.log(this); // window
// return a-b
// })
// function fn(a){
// a()
// }
// fn(function(){
// console.log(this)
// })
// setTimeout(()=>{
// console.log(this) // window
// }, 2000)
/* var num = 100;
var obj = {
num: 2,
fn: function () {
var num = 1;
console.log(this) //window
(function (num) { // 100
// this-> window
console.log(this.num + num);//200
})(this.num)
// this->window
}
}
// obj.fn();
var f = obj.fn;
f() */
var num = 1; // 1 2
var obj = {
num: 0,
fn: function () {
num = 1;
// this=>obj
(function (num) { // 0 1
// this=>window
++this.num;
num++;
console.log(num)
})(this.num)
}
}
obj.fn();
console.log(window.num, obj.num);
改变this指向的方法
每一个函数都是Function的实例,所以每一个函数都可以调取Function原型上的方法,call,apply,bind,他们三个都可以改变函数里的this指向
call
- call继承:私有属性
- fn通过__proto__属性找到当前所属类的原型(Function的原型)上的call方法
- 所有函数可以获取到call
- 让call方法执行,并且给call传递实参
- 在call方法执行的同时,也让fn执行,并且把fn的this指向了第一个参数
- 注意事项
- 在严格模式下,如果call不传参或者传undefined,那fn的this就是undefined,如果传null,那fn的this就是null
- 在非严格模式下,如果call不传参或者传undefined或者传null,那fn的this都是window
- call的第一个参数是fn的this指向,从第二个开始,就是fn的正常参数了
- fn通过__proto__先找到Function原型中的call方法,让call方法执行,call运行时,改变了call的this的this指向,fn中的this指向call的第一个参数,并且让call中this执行;
function fn1(){
console.log(100);
console.log(this);
}
function fn2(){
console.log(200);
}
//fn.call(1)
fn1.call.call.call.call(fn2);
// 1. fn1.call.call.call -->this--> fn2;
// 2. fn1.call.call.call()
// 1.fn2--> this 没有变
// 2.fn2();
// 1.先执行后面的call方法(这个call方法中的this是fn1.call);
是改变fn1.call中的this指向fn2;并且让fn1.call运行;
// 2. 当fn1.call运行时,改变fn.call中的this的this指向没有发生改变,
继续让fn1.call中this执行,也就是让fn2运行;
-------------------------------------------------------
function B(){
}
console.log(B.name);// 对象
function A(){
}
A.call()
function B(){
}
A.call.call.call(B);// B执行,并且B中的this一定指向window
最后一个call执行时, 把A.call.call中的this改成了函数B;并且让A.call.call执行;
当A.call.call执行时,把函数B的this改成window,并且让B执行;
// "use strict"
function fn(n,m){
console.log(this)
}
let obj = {
name:3
}
// fn(12,13)
// 实现把函数里的this改为obj
// obj.fn = fn;
// obj.fn();
// delete obj.fn;
fn.call(undefined)
call方法封装
function myCall(context, ...arg) { // 收缩运算符
// arg接收的是传递的从第二个开始的实参
// this->fn context->obj
let res = null; // 初始化一个实例的返回值
context = context || window
// 处理传参的特殊情况,如果传的是空、null和undefined,context的值就是window
context.$fn = this // 把当前实例放到对象里
res = context.$fn(...arg); // 让this执行(让当前实例执行)
delete context.$fn // 在对象里删除那个实例
return res;// 把this执行之后的返回值return 出去
}
Function.prototype.myCall = myCall;
function fn() {
console.log(this)
return 1
}
let obj = {
name: 3
}
// console.log(fn.call(1))
console.log(fn.myCall(obj, 12, 23))
console.log(obj)
连call面试题
- 如果有两个及以上call,那最后就是执行传入的参数
<script>
function fn1() {console.log(1)}
function fn2() {console.log(2)}
// fn1.call(fn2); //1
// fn1.call.call(fn2); //2 不管前边有多少call,他执行的是最后一个call方法
// Function.prototype.call(fn1); // 不输出
// Function.prototype.call.call(fn1); // 1
Function.prototype.call.call.call.call(fn1);
function myCall(context){
// 如果有两个及以上call,那最后就是执行传入的参数,传入的参数的this指向window
/*
fn1.call(fn2);
context->fn2 this->fn1
context.$fn = this ->fn2.$fn = fn1
context.$fn() ->fn1()
*/
/*
fn1.call.call(fn2)
context ->fn2 this-> fn1.call ->call
context.$fn = this ->fn2.$fn = call
context.$fn() ->fn2.$fn() ->call()
第二次执行
context->window this->fn2
context.$fn = this ->window.$fn = fn2;
context.$fn() ->window.$fn() ->fn2()
*/
/*
Function.prototype.call(fn1)
context ->fn1 this->Function.prototype【原型】
context.$fn = this ->fn1.$fn = 【原型】
context.$fn() ->fn1.$fn() ->【原型】()
*/
/*
Function.prototype.call.call(fn1)
context ->fn1 this -> Function.prototype.call ->call
context.$fn = this ->fn1.$fn = call
context.$fn() ->fn1.$fn() ->call()
第二次执行
context->windwo this->fn1
context.$fn = this ->window.$fn = fn1;
context.$fn() ->window.$fn ->fn1()
*/
context.$fn = this;
context.$fn()
}
</script>
apply
- 改变this指向:他和call方法一样,只不过传参不同,第二个参数必须是数组或者类数组
- 传入数组,但是Fn实际接收的仍然是一个一个接收;
function fn(a,b){
console.log(a,b);
console.log(this);
}
fn.apply(null,[100,200])
----------------------------------------
function fn(n,m){
console.log(this, n, m)
}
fn.apply(1, [20,30])
bind
- 预处理this
- 这个方法也是改变this指向的,但他会提前改变实例函数的this指向,并不会让实例函数执行,他的返回值是改变this之后的新函数
- 在bind函数中将fn进行了包装和处理,改变了fn里面的this指向,并且返回一个改变this之后的新函数
- bind在IE8以下不兼容,bind正常传参
function fn(a,b){
console.log(a,b);
console.log(this);
}
var f = fn.bind([1,2]);
f();
var f =fn.bind([1,2]);
f(100,200);
fn();
--------------------------------------------------------
<div id="box">1111</div>
// let box = document.getElementById('box');
let fn = function(){
console.log(this)
}
let obj = {}
// box.onclick = fn.bind(obj)
fn = fn.bind(obj)//不改变原有函数,需要重新赋值
fn()