知识点汇总

156 阅读9分钟

1.数据类型

js的数据类型分为两大类,九个数据类型:

  1. 对象类型
  2. 原始类型

原始类型又分为7个基本类型:

  1. string
  2. number
  3. boolean
  4. symbol
  5. null
  6. undefined
  7. bigInt

对象类型分为两种:

  1. object
  2. function

其中object又分为:

  1. Array
  2. RegExp
  3. Date
  4. Map
  5. Set
  6. Math
  7. ...

原始类型存储在栈上,对象类型存储在堆上,但是他的引用还是在栈上

image.png

栈内存中的变量一般都是已知大小或者有范围上限的,而堆内存中存储的对象大小一般都不固定,这也是为什么null作为一个object类型的变量却存储在栈内存中的原因。

考点:const定义的值能改么?

答案是部分能改,部分不能改。const定义的基本类型不能改变,但是定义的对象是可以通过修改对象属性等方法来改变的。
当我们定义const对象的时候,我们说的常量其实是指针,就是const对象对应的堆内存指向是不变的,但是堆内存中的数据大小和属性是不固定的,而对于const定义的基础变量而言,这个值就相当于const对象的指针,是不可变。每次使用const或者let去初始化一个变量的时候,会首先遍历当前的内存栈,看看有没有重名变量,有的话就返回错

使用new关键字初始化的之后是不存储在栈内存中的,根据构造函数生成新实例,这个时候生成的是对象,而不是基本类型

var a = new String('123')
var b = String('123')
var c = '123'
console.log(a==b, a===b, b==c, b===c, a==c, a===c)	
>>> true false true true true false
console.log(typeof a)
>>> 'object'

我们可以看到new一个String,出来的是对象,而直接字面量赋值和工厂模式出来的都是字符串。但是根据我们上面的分析大小相对固定可预期的即便是对象也可以存储在栈内存的,比如null,为啥这个不是呢

var a = new String('123')
var b = new String('123')
console.log(a==b, a===b)
>>> false false

很明显,如果a,b是存储在栈内存中的话,两者应该是明显相等的,就像null === null是true一样,但结果两者并不相等,说明两者都是存储在堆内存中的,指针指向不一致。

说到这里,再去想一想我们常说的值类型和引用类型其实说的就是栈内存变量和堆内存变量,再想想值传递和引用传递、深拷贝和浅拷贝,都是围绕堆栈内存展开的,一个是处理值,一个是处理指针。

2.类型判断

2.1 typeof

原始类型中除了 null,其它类型都可以通过 typeof 来判断。对于对象类型来说,typeof 只能具体判断函数的类型为 function,其它均为 object。

image.png

2.2 instanceof

instanceof 内部通过原型链的方式来判断是否为构建函数的实例,常用于判断具体的对象类型。

// true
[].constructor === Array

2.3 Object.prototype.toString

image.png

3.4 isXXX API

image.png

3.类型转换

类型转换分为两种情况,分别为强制转换及隐式转换。

3.1 强制转换

Number(false) // -> 0
Number('1') // -> 1
Number('zb') // -> NaN
(1).toString() // '1'

转布尔值规则:

  • undefined、null、false、NaN、''、0、-0 都转为 false。
  • 其他所有值都转为 true,包括所有对象。

转数字规则:

  • true 为 1,false 为 0
  • null 为 0,undefined 为 NaN,symbol 报错
  • 字符串看内容,如果是数字或者进制值就正常转,否则就 NaN
  • 对象的规则隐式转换再讲

3.2 隐式转换

  1. 如果类型相同,则无须进行类型转换
  2. 如果其中一个操作值是null或者undefined,那么另外一个操作符必须为null或者undefined才会返回true、否则都返回false
  3. 如果其中一个是Symbol类型,那么返回false
  4. 两个操作值如果都为string和number类型。那么就会将字符串转换为number
  5. 如果一个操作值是boolean,那么转换成number
  6. 如果一个操作值为object,且另一方为string、number或者symbol,就会把object转为原始类型再判断
null == undefined // true 规则2
null == 0 // false 规则2
'' == null // false 规则2
'' == 0 // true 规则4
'123' == 123 // true 规则4
0 == false // true
1 == true // true
var a = {
    value:0,
    valueOf:function(){
        this.value ++
        return this.value    
    }
}
console.log(a==1 & a==2&&a==3) // true

4.this 参考

  1. 查看函数在哪被调用。
  2. 点左侧有没有对象?如果有,它就是 “this” 的引用。如果没有,继续第 3 步。
  3. 该函数是不是用 “call”、“apply” 或者 “bind” 调用的?如果是,它会显式地指明 “this” 的引用。如果不是,继续第 4 步。
  4. 该函数是不是用 “new” 调用的?如果是,“this” 指向的就是 JavaScript 解释器新创建的对象。如果不是,继续第 5 步。
  5. 是否在“严格模式”下?如果是,“this” 就是 undefined,如果不是,继续第 6 步。
  6. JavaScript 很奇怪,“this” 会指向 “window” 对象。
  7. 箭头函数在自己的作用域内没有自己的 this,如果要使用 this ,就会指向定义时所在的作用域的 this 值

5.作用域和闭包

5.1编译原理

编译有三个阶段,分别是:分词/词法分析、解析/语法分析、代码生成。

  1. 分词/词法分析:将由字符组成的字符串分解成有意义的代码,var a = 2;,将被分解成 var、a、=、2、;
  2. 解析/语法分析:将词法单元转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树,这个树被称做‘抽象语法树’(AST),指的是源代码语法所对应的树状结构。也就是说,对于一种具体编程语言下的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每一个节点上
  3. 代码生成:将AST转换成可执行的代码

下面我们将 var a = 2; 分解,看看引擎和它的朋友们是如何协同工作的:

编译器首先将这段程序分解成词法单元,然后将词法单元解析成一个对象树,在执行代码阶段,遇到var a,编译器会询问作用域中是否有a这个变量,如果有,编译器会忽略该声明继续编译,如果没有,就会要求作用域声明这个变量,命名为a,进行到a = 2时向作用域询问是否存在a这个变量,如果有则赋值,没有向上继续查找,都没有就会抛出异常。

引擎会为变量 a 进行 LHS 查询。另外一个查找的类型叫作 RHS。如果查找的目的是对变量进行赋值,那么就会使用 LHS 查询;如果目的是获取变量的值,就会使用 RHS 查询。

function foo(a) { 
 var b = a; 
 return a + b; 
} 
var c = foo( 2 );
// 1. 找出所有的 LHS 查询(这里有 3 处!)c = ..;、a = 2(隐式变量分配)、b = ..
// 2. 找出所有的 RHS 查询(这里有 4 处!)foo(2..、= a;、a ..、.. b

5.2预编译

函数声明整体提升,变量声明提升

test()
function test(){
    console.log('a')
}
// 可以打印出结果

console.log(a)
var a = 123
// 打印undefined

预编译有四个步骤:

  1. 创建AO对象
  2. 找形参和变量声明,将变量声明和形参名作为AO属性名,值为undefined
  3. 将实参值和形参统一
  4. 在函数体里面找函数声明,值赋予函数体
function fn(a){
    console.log(a)
    var a = 123
    console.log(a)
    function a(){}
    console.log(a)
    var b = function b(){}
    console.log(b)
    function d(){}
}
fn(1)
打印如下:
// function a(){}
// 123
// 123
// function b(){}
// 函数声明提升

预编译过程如下:

  • 创建AO对象
  • 找形参和变量声明,将变量声明和形参名作为AO属性名,值为undefined AO:{ a:undefined b:undefined d:undefined }
  • 将实参值和形参统一 AO:{ a:1 b:undefined d:undefined }
  • 在函数体里面找函数声明,值赋予函数体 AO:{ a:function a(){} b:undefined d:function d(){} }

函数执行:按顺序执行

例子

global = 100
function fn(){
    console.log(global)
    global = 200
    console.log(global)
    var global = 300
}
fn()
var global

// 打印如下:
undefined
200

因为在执行函数的时候,会优先找函数内部的global,如果没有,才会去上级找。 函数内部的预编译过程:

  • 创建AO对象
  • 找形参和变量声明,将变量声明和形参名作为AO属性名,值为undefined AO = { global:undefined }
  • 将实参值和形参统一 AO = { global:undefined }
  • 在函数体里面找函数声明,值赋予函数体 AO = { global:undefined } 按顺序执行,所以第一个console.log()打印出来的是underfined,执行到 global = 200时,global才被赋值

5.3作用域

[[scope]] 每个JavaScript函数都是一个对象,对象中有些属性我们可以访问,但是有些不可以,这些属性仅供JavaScript引擎存取,[[scope]]就是其中一个 [[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。

JavaScript 采用的是词法作用域(静态作用域),函数的作用域在函数定义的时候(书写阶段)就决定了。而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。JavaScript采用的是静态作用域,this采用的是动态作用域:

var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar(); //打印1

5.4 闭包

首先闭包正确的定义是:假如一个函数能访问外部的变量,那么这个函数它就是一个闭包,而不是一定要返回一个函数。

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[5](); // 10

此处i是全局变量,for循环结束后,i=10,function只是被赋值,不被执行,即,a[0] = function(){console.log(i)},a[1]=function(){console.log(i)},...,不管a数组里有几个值,都只是赋值function,而不执行,console.log()只是在调用a5的时候才执行,那时候的i已经变为10,所以,不管a[i]中的i为多少,打印的都是10

题目

// 1
for ( var i = 0 ; i < 5; i++ ) {
    setTimeout(function(){
        console.log(i);
    }, 0);
}
// 5 5 5 5 5


// 2
for ( var i = 0 ; i < 5; i++ ) {
    (function(j){
        setTimeout(function(){
            console.log(j);
        }, 0);
    })(i);
    // 这样更简洁
    // setTimeout(function(j) {
    //     console.log(j);
    // }, 0, i);
}
// 0 1 2 3 4

setTimeout(function(j) {
    console.log(j);
}, 0, i);

// 3
for ( let i = 0 ; i < 5; i++ ) {
    setTimeout(function(){
        console.log(i);
    },0);
}
// 0 1 2 3 4


// 4
var scope = 'global scope';
function checkscope(){
    var scope = 'local scope';
    console.log(scope);
}
// local scope


// 5
var scope = 'global scope';
function checkscope(){
    var scope = 'local scope';
    return function f(){
        console.log(scope);
    };
}
var fn = checkscope(); 
console.log(fn()); // local scope


// 6
var scope = 'global scope';
function checkscope(){
    var scope = 'local scope';
    return function f(){
        console.log(scope);
    };
}
checkscope()(); // local scope


var obj = {
    name: 'tom',
    sayName() {
        console.log(this.name);
    }
}
obj.sayName(); // tom


var obj = {
    name: 'tom',
    sayName() {
        var name = 'alan';
        console.log(this.name);
    }
}
obj.sayName();// 'tom'

var name = 'jerry';   
var obj = {  
    name : 'tom',  
    sayName(){  
        return function(){  
            console.log(this.name);  
        };
    }   
};  
obj.sayName()(); // jerry 因为执行obj.sayName()时return函数,然后此时再执行obj.sayName()(),this.name就应该在全局中取

// var name = 'jerry';
var obj = {
    name: 'tom',
    sayName() {
        var name = 'alan';
        console.log(this.name);
    }
};
var sayName = obj.sayName; 此时 只是赋值obj中的sayName函数,并没有被执行,当执行时,作用域发生改变,此时this指向window,而window.name为空字符串
sayName(); // '' // jerry 


function fun(a,b) {
    console.log(b)
    return {
        fun: function(c) {
            return fun(c,a);
        }
    };
}
var d = fun(0); // undefined
d.fun(1); // 2 0
d.fun(2); // 2 0
d.fun(3); // 2 0

var d1 = fun(0).fun(1).fun(2).fun(3);
// 2 undefined 2 0
// 2 1
// 2 2

var d2 = fun(0).fun(1); d2.fun(2);
// 2 undefined
// 2 0
// 2 1
// 2 1

d2.fun(3); // {fun: ƒ}

6.new

new的过程:

  1. 新生成了一个对象
  2. 对象连接到构造函数原型上,并绑定 this
  3. 执行构造函数代码
  4. 返回新对象

考点:

  • new 做了那些事?
  • new 返回不同的类型时会有什么表现?
  • 手写 new 的实现过程

7.原型

image.png

  • 所有对象都有一个属性 proto 指向一个对象,也就是原型
  • 每个对象的原型都可以通过 constructor 找到构造函数,构造函数也可以通过 prototype 找到原型
  • 所有函数都可以通过 proto 找到 Function 对象
  • 所有对象都可以通过 proto 找到 Object 对象
  • 对象之间通过 proto 连接起来,这样称之为原型链。当前对象上不存在的属性可以通过原型链一层层往上查找,直到顶层 Object 对象,再往上就是 null 了

8.继承

8.1 原型链继承

每个构造函数都有一个原型对象,原型对象又包含有个指向构造函数的指针,而实例则包含一个原型对象的指针(内存空间是共享的,当一个发生变化的时候,另一个也随之变化,构造函数的指向也发生变化)

function Parent1(){
    this.name = 'parent1'
    this.play = [1,2,3]
}
function Child1(){
    this.type = 'child2'
}
Child1.prototype = new Parent1()
console.log(new Child1())
var s1 = new Child1()
var s2 = new Child1()
s1.play.push(4)
console.log(s1.play,s2.play) // [1,2,3,4] [1,2,3,4]


8.2 构造函数继承 (借助call)

---父类的原型方法不会被继承,只继承属性和方法

function Parent1(){
    this.name = 'parent1'
}
Parent1.prototype.getName = function(){
    return this.name
}
function Child1(){
    Parent1.call(this)
    this.type = 'child1'
}
let child = new Child1()
console.log(child) // {name:'parent1',type:'child1''}
console.log(child.getName()) //报错

8.3 组合继承

function Parent3(){
    this.name = 'parent3'
    this.play = [1,2,3]
}
Parent3.prototype.getName = function (){
    return this.name
}
function Child3(){
    Parent3.call(this)
    this.type = 'child3'
}
Child3.prototype = new Parent3()
Child3.prototype.constructor = Child3
var s3 = new Child3()
var s4 = new Child3()
s3.play.push(4)
console.log(s3.play,s4.play) // [1,2,3,4] [1,2,3]
console.log(s3.getName()) // parent3
console.log(s4.getName()) // parent3

8.4 原型式继承 (如果有相同属性,可能会被篡改)

let parent4 = {
    name:'parent4',
    friends:["p1","p2","p3"],
    getName:function(){
        return this.name    
    }
}
let person4 = Object.create(parent4)
person4.name = "tom"
person4.friends.push("jerry")
let person5 = Object.create(parent4)
person5.friends.push("lucy")
console.log(person4.name) // "tom"
console.log(person4.name === person4.getName()) // true
console.log(person5.name) // parent4
console.log(person4.friends) // ["p1","p2","p3","jerry","lucy"]
console.log(person5.friends)//  ["p1","p2","p3","jerry","lucy"]

8.5 寄生式继承

使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力在进行增强添加一些方法

let parent5 = {
    name:'parent5',
    friends:["p1","p2","p3"],
    getName:function(){
        return this.name    
    }
}
function clone(original){
    let clone = Object.create(original)
    clone.getFriends = function(){
        return this.friends    
    }
    return clone
}
let person5 = clone(parent5)
console.log(person5.getName())  // parent5
console.log(person5.getFriends()) // ["p1","p2","p3"]

8.6 寄生组合式继承

function clone(parent,child){
    child.prototype = Object.create(parent.prototype)
    child.prototype.constructor = child
}
function Parent6(){
    this.name = 'parent6'
    this.play = [1,2,3]
}
Parent6.prototype.getName = function(){
    return this.name
}
function Child6(){
    Parent6.call(this)
    this.friends = 'child5'
}
clone(Parent6,Child6)
Child6.prototype.getFriends = function(){
    return this.friends
}
let person6 = new Child6()
console.log(person6) // {name:"parent6",play:[1,2,3],friends:"child5"}
console.log(person6.getName()) // parent6
console.log(person6.getFriends()) // child5 

8.7 圣杯模式

function inherit(Target,Origin){
    function F(){}
    F.prototype = Origin.prototype
    Target.prototype = new F()
    Target.prototype.constuctor = Target
    Target.prototype.uber = Origin.prototype
}

//上面的函数可以这样写

var inherit = (function (){
    var F = function (){}
    return function(Target,Origin){
        F.prototype = Origin.prototype
        Target.prototype = new F()
        Target.prototype.constuctor = Target
        Target.prototype.uber = Origin.prototype
    }
}())

// 这样写,是利用闭包让F()私有化

function Father(){}
function Son(){}
inherit(Son,Father)
var son = new Son()
var father = new Father()

如果想实现函数连续调用,可以这么写

var obj = {
    add:function(){
        console.log('add')
        return this
    },
    push:function(){
        console.log('push')
        return this
    },
    slice:function(){
        console.log('slice')
        return this
    }
}

obj.add().push().slice()
var obj = {
    fn1:{name:'fn1'},
    fn2:{name:'fn2'},
    fn3:{name:'fn3'},
    output:function(num){
        return this['fn' + num]
    }
}  
obj.output(1)
//{name: "fn1"}

提问:

var num = 123
num.toString()
//"123"
true.toString()
//"true"
'123'.toString()
//"123"
[].toString()
//""
然而
var obj = {}
obj.toString()
"[object Object]"

因为String,Number,Boolean,Array原型上已经重写了toString方法,Object没有重写,所以优先调用本身原型的方法,如果想调用未重写的方法,可以使用call改变this指向,Object.prototype.toString.call(要toSting的值)

提问:Object.create() 和 new的区别

// new
function Parent(){
    this.name = 'parent'
    this.play = [1,2,3]
}
function Child(){
    this.type = 'child'
}

var parent = new Parent()
Child.prototype = parent
Child.prototype.constructor = Child
let child = new Child();

console.log(parent.constructor === child.constructor)

parent.constructor === child.constructor  // true 都指向Child


// Object.create()

function Parent(){
    this.name = 'parent'
    this.play = [1,2,3]
}
function Child(){
    this.type = 'child'
}

let parent = new Parent();
Child.prototype = Object.create(new Parent())

Child.prototype.constructor = Child

let child = new Child(); 

console.log(parent.constructor === child.constructor)

// 原生写法

function Parent(){
    this.name = 'parent'
    this.play = [1,2,3]
}
function Child(){
    this.type = 'child'
}

let parent = new Parent();
Child.prototype = parent

var F = function() {} //

let f = new F(); //
F.prototype = Parent.prototype //这四行就是Object.create

Child.prototype = new F(); //

Child.prototype.constructor = Child; //

let child = new Child(); //

console.log(parent.constructor === child.constructor) // false 指向各自的构造函数

常见考点

  • JS 中如何实现继承
  • 通过原型实现的继承和 class 有何区别
  • 手写任意一种原型继承

9.深浅拷贝

9.1 浅拷贝

我们可以通过 assign 、扩展运算符等方式来实现浅拷贝:

let a = {
    age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1
b = {...a}
a.age = 3
console.log(b.age) // 2

第二种手写:

function clone(origin,target){
    var target = target || {}
    for(var prop in origin){
        target[prop] = origin[prop]
    }
    return target
}

9.2 深拷贝

最简单的深拷贝方式就是使用 JSON.parse(JSON.stringify(object))

function deepClone(origin,target){
    var target = target || {}
    for(var prop in origin){
        if(origin.hasOwnProperty(prop)){
           if(origin[prop] !== null && typeof(origin[prop]) === 'object'){
              if(Object.prototype.toString.call(origin[prop]) == '[object Array]'){
                  target[prop] = []
              }else{
                  target[prop] = {}
              }
              deepClone(origin[prop],target[prop])  
           }else{
               target[prop] = origin[prop]
           }
        }
    }
    return target
}

10.任务队列

/**
 * 微任务是在宏任务里执行的,只有把一个宏任务里的所有微任务都执行完,才会开始下一个宏任务,这个过程就叫Event-loop
 **/
 宏任务包括:I/O、setTimeoutsetInterval、setImmediate、requestAnimationFrame(这个意思是重绘,每个浏览器重绘的方式都是不一样的,和setTimeout在一起的时候,不一定谁先执行,完全取决于浏览器)
 微任务包括:process.nextTickMutationObserverPromise.then catch finally  ,
 promise在then/catch/finally前,都是同步执行
                 
console.log('1');
setTimeout(function () {
    console.log('2');
    process.nextTick(function () {
        console.log('3');
    })
    new Promise(function (resolve) {
        console.log('4');
        resolve();
    }).then(function () {
        console.log('5')
    })
})
process.nextTick(function () {
    console.log('6');
})
new Promise(function (resolve) {
    console.log('7');
    resolve();
}).then(function () {
    console.log('8')
})
setTimeout(function () {
    console.log('9');
    process.nextTick(function () {
        console.log('10');
    })
    new Promise(function (resolve) {
        console.log('11');
        resolve();
    }).then(function () {
        console.log('12')
    })
})  //1 7 6 8 2 4 3 5 9 11 10 12

  1. 遇到setTimeout,其回调函数被分发到宏任务Event Queue中
  2. 遇到process.nextTick(),其回调函数被分发到微任务Event Queue中
  3. 遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中
  4. 又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,第一次时间循环结束,第二次循环类似
async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2')
}
console.log('script start')
setTimeout(function () {
  console.log('setTimeout')
}, 0)
async1()
new Promise(function (resolve) {
  console.log('promise1')
  resolve()
}).then(function () {
  console.log('promise2')
})
console.log('script end')


// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
  1. 整体 script 作为第一个宏任务进入主线程,代码自上而下执行,执行同步代码,输出 script start
  2. 遇到 setTimeout,加入到宏任务队列
  3. 执行 async1(),输出async1 start;然后遇到await async2(),await 实际上是让出线程的标志,首先执行 async2(),输出async2;把 async2() 后面的代码console.log('async1 end')加入微任务队列中,跳出整个 async 函数。(async 和 await 本身就是 promise+generator 的语法糖。所以 await 后面的代码是微任务。)
  4. 继续执行,遇到 new Promise,输出promise1,把.then()之后的代码加入到微任务队列中
  5. 继续往下执行,输出script end。接着读取微任务队列,输出async1 end,promise2,执行完本轮的宏任务。继续执行下一轮宏任务的代码,输出setTimeout

setTimeout(function () {
  console.log('timer1')
}, 0)

requestAnimationFrame(function () {
  console.log('requestAnimationFrame')
})

setTimeout(function () {
  console.log('timer2')
}, 0)

new Promise(function executor(resolve) {
  console.log('promise 1')
  resolve()
  console.log('promise 2')
}).then(function () {
  console.log('promise then')
})

console.log('end')

// 谷歌浏览器
promise 1
promise 2
end
promise then
requestAnimationFrame
timer1
timer2

火狐浏览器

promise 1
promise 2
end
promise then
timer1
timer2
requestAnimationFrame
  1. 整体 script 代码执行,开局新增三个宏任务,两个 setTimeout 和一个 requestAnimationFrame
  2. 遇到 Promise,先输出promise1, promise2,加把 then 回调加入微任务队列。
  3. 继续往下执行,输出end
  4. 执行 promise 的 then 回调,输出promise then
  5. 接下来剩三个宏任务,我们可以知道的是timer1会比timer2先执行,那么requestAnimationFrame呢?

requestAnimationFrame是什么呢

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。 requestAnimationFrame 的基本思想是 让页面重绘的频率和刷新频率保持同步,相比 setTimeout,requestAnimationFrame 最大的优势是由系统来决定回调函数的执行时机。

11.call,apply,bind

11.1 call

function a(){ 
    console.log(this,'a')
};
function b(b){
    console.log(b)
}
a.call(b,'我是B的参数')

// 打印
ƒ b(b){console.log(b)} "a"

// 此时,调用a函数时,this的指向已经变成了b函数,后面为传的参数

手写: 1.将函数设为对象的属性 2.执行该函数 3.删除该函数

Function.prototype.myCall = function (context) {
    var context = context || window

    context.fn = this

    // let arr = []
    // for (let i = 1; i < arguments.length; i++) {
    //     arr.push(arguments[i])
    // }
    
    let arg = [...arguments].slice(1)
    var result = context.fn(...arg)
    delete context.fn

    return result
}

var foo = {
    value: 1
}

function bar(name) {
    console.log(name)
    console.log(this.value)
}

bar.myCall(foo, 2)

11.2 apply

Function.prototype.myApplay = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        result = context.fn(...arr)
    }

    delete context.fn
    return result;
}

var foo = {
    value: 1
}

function bar(name,age) {
    console.log(name,age)
    console.log(this.value)
}

bar.myApplay(foo, [2,5])

11.3 bind

12.枚举

12.1 for...in

var obj = {
    name:'ljl',
    age:22,
    sex:'男',
    tel:15959172873
}

for(var key in obj){
    console.log(obj.key) // 此时会打印出4个undefined
    console.log(obj[key]) // 此时会打印 ljl 22 男 15959172873
    
    // 因为obj.key ---> obj['key'] ,把key当做属性,访问的是属性,然而obj对象中就没有key这个属性
}

12.2 hasOwnProperty

for...in循环是可以遍历原型里的属性,如果不想访问原型里的属性,可以使用hasOwnProperty,属性是否只存在于本身,除原型以外,是的话返回true

var obj = {
    name:'ljl',
    age:22,
    sex:'男',
    tel:15959172873,
    __proto__:{
        type:'proto'
    }
}
for(var key in obj){
    if(obj.hasOwnProperty(key)){
        console.log(obj[key])
    }
}

12.3 in

属性不管在本身还是原型内,都返回true

12.4 instanceof

A instanceof B ,A对象是不是B构造函数构造出来的,看A对象的原型是否有B的原型

13.数组

13.1 push

实现:

Array.prototype.push = function(){
    for(var i = 0;i<arguments.length;i++){
        this[this.length] = arguments[i]
    }
    return this.length
}

13.2 类数组 arguments

属性为索引(数字)属性,必须要有length属性,最好有push方法

var obj = {
    "0":"a",
    "1":"b",
    "2":"c",
    "length":3,
    "push":Array.prototype.push
}

obj.push("d") 
// 打印 
{0: "a", 1: "b", 2: "c", 3: "d", length: 4, push: ƒ}

题目:

var obj = {
    "2":"a",
    "3":"b",
    "length":2,
    "push":Array.prototype.push
}

obj.push("c")
obj.push("d")
// 打印出
var obj = {
    "2":"c",
    "3":"d",
    "length":4,
    "push":Array.prototype.push
}
执行push的时候,在length取索引,此时索引为2,所以在索引2处放入"c",然后length++,在索引为3处放入"d",length++为4

14 立即执行函数

var fn = (function(){
    console.log(111)
}())

运行完自动回收