JavaScript 基础总结(2)

2018-08-24  本文已影响13人  千反田爱瑠爱好者

原型链

var s = new String()                        
// 公式:var 对象 = new 函数()
s.__proto__ === String.prototype             
String.prototype.__proto__ === Object.prototype
s.__proto__.__proto__ === Object.prototype

// 对象的构造函数
Function.__proto__ === Function.prototype
Array.__proto__ === Function.prototype
Object.__proto__ === Function.prototype

// 从对象s的属性到String类型的共有属性(__proto__),再到Object的共有属性(__proto__.__proto__)构成一条原型链
// 其中prototype是浏览器window对象所指的复杂类型(Number、String...)事先准备好的,自定义对象的__proto__用于引用对应类型的prototype
// 所以String.prototype是String的共有属性,s.__proto__是String共有属性的引用

对于一个变量是否存在某属性,会顺着原型链依次查找,当执行某方法或获取某属性(如o.toString)时,会先判断是否对象:

__proto__

关于函数:

Object.__proto__ === Function.prototype     // Function是Object的构造函数
Object.prototype.__proto__ === null         // Object.prototype已经是末端,不存在其引用的共有属性

数组

Array基本用法:构造数组对象

数组与对象

当声明一个数组let a = [1, 2, 3]或一个对象let b = {0: 1, 1: 2, 3: 3, length: 3}其含义是:

a[0] === b[0] === 1
a[1] === b[1] === 2
1[2] === b[2] === 3
a.length === b.length === 3
a.__proto__ === Array.prototype    // 数组的共有属性
b.__proto__ === Object.prototype   // 对象的共有属性

但前者称为数组、后者称为对象的根本原因是共有属性(原型链)不一样,当向数组加入数值以外的key:

a.xxx = 1
a.yyy = 2

使用两种方式遍历会出现不同结果():

for (let i = 0; i < a.length; i++) {
    console.log(a[i])
}    // 输出1, 2, 3,不关心是否数组,只要存在数值索引即可这样遍历(以对象方式定义也是一样)
for (let key in a) {
    console.log(key)
}    // 输出1, 2, 3, xxx, yyy

伪数组

当对象的原型链中没有Array.prototype则称为伪数组:

let a = {0: 1, 1: 2, 2: 3, length: 4}

常用的伪数组是arguments,表示向函数传入的参数(不能执行push等方法):

function f() {
    console.dir(arguments)
}
f(1, 2, 3)

常用API

a.forEach(function(x, y){    // 对数组中的每个元素执行传入的匿名函数
    console.log('value', x)    // 注意顺序,两个参数必然为value-key-array
    console.log('key', y)
})        

function forEach(array, func) {    // 等价于向函数传入两个参数(数组和函数)
    for (let i = 0; i < array.length; i++)
        func(array[i], i)
}

a.sort(function(x, y){return x - y})      // 按特定规则排序(从小到大,满足第一个参数比第二个参数大,返回Ture)
a.join(', ')    // 数组转字符串(默认以逗号)
a.concat(a)        // 拼接
a.map(x => x % 2)             // 对数组执行特定函数,返回新的数组
a.filter(x => x % 2 == 1)    // 过滤符合条件的元素
a.reduce((x, y) => x + y)    // 归约数组元素

函数

声明方式

// 具名函数
function f(a, b) {return a + b}    // f.name === 'f'

// 匿名函数(不能单独使用) 
var f = function(a, b) {return a + b}    // f.name === 'f'

// 具名函数赋值
var f = function y(a, b) {return a + b}    // y不存在,f.name === 'y'

// window.Function对象
var f = new Function('x', 'y', 'return x + y')    // 最后一个参数为返回语句(可以动态定义),f.name === 'anonymous'

// 箭头函数
var f = (x, y) => x + y    // 只有一句话,而且不能带对象,f.name === 'f'

本质

this与arguments

每个函数都有自己的this和arguments参数,都要再函数调用(call)时才确定:

call stack

每次发生函数调用的地方把当前执行函数入栈、执行内部逻辑,完成后再弹出,继续往下执行。

function a() {
    console.log('a')
    b.call()
    return 'a'
}

function b() {
    console.log('b')
    c.call()
    return 'b'
}

function c() {
    console.log('c')
    return 'c'
}

a.call()

递归

function sum(n) {
    if (n == 1) {
        return 1
    } 
    else {
        return n + sum.call(undefined, n - 1)
    }
}
sum.call(undefined, 5)

作用域

作用域以树的形式表示,就近原则:

var a = 1                // 全局作用域
function f1() {          // 全局作用域
    f2.call()            
    console.log(a)       // undefined
    var a = 2            // 变量提升
    function f2() {      // f1作用域
        var a = 3        // f2作用域
        console.log(a)
    }
    f4.call()
}
function f4() {
    console.log(a)      // 1
}
// 在此处修改a的值,则会影响f4的输出,因为f4输出的是第一行声明的a,这个a在此处被修改,然后才执行f1内部的f4
f1.call()
console.log(a)

a = 1更可能是对已声明变量赋值,从当前作用域开始向父作用域查找(先检查当前作用域中前面代码是否有声明、是否存在变量提升),直到当全局作用域都没有声明,才会认为是声明且赋值。

函数非当场执行,相关变量就有被修改的可能,经典易错题:

// 假设存在6个<li>标签

var liTags = document.querySelectorAll('li')
for (var i = 0; i < liTags.length; i++) {
    liTags[i].onclick = function() {
        console.log(i)
    }
}

// 当点击li标签时,输出的应该是6
// console.log(i)所输出的i是for循环的i,这个i的值在for循环结束(也就是为li标签加上onclick事件)时被修改为6,所以后续每再次访问结果都为6

闭包

如果一个函数使用其作用域范围以外的变量,则这个函数、这个变量称为闭包:

var a = 1
function f4() {
    console.log(a)
}
function f1() {
    var n = 9;
    function f2() {
        console.log(n);
    }
    return f2;
}
var f2 = f1();  // 函数f1的返回值是函数f2
f2();           // f2可以读取f1的内部变量,所以调用f2时就可以获取f1的内部变量n
function f1(n) {
    return function () {
        return n++;
    };
}
var a = f1(1);
a()    // 1
a()    // 2
a()    // 3,内部变量记录每次调用结果会被记录
function f1(n) {
    return function () {
        return n++;
    };
}
var a1 = f1(1)
var a2 = f2(2)
a1()
a1()
a2()
a2()    // 每次调用a1和a2返回的结果都不同,因为a1和a2内部变量是相互独立的,会返回各自的内部变量

继承

JS函数可以产生对象,因此也可以作为类:

function Human(name) {
    this.name = name
}
let person = new Human("ywh")

继承即让子类具有父类的属性和方法:

let a = new Array()
a.push()        // 实例属性,源于Array.prototype,a的原型中
a.valueOf()     // 继承属性,源于Array.prototype.__proto__,a的原型的原型中

ES5实现继承(修改原型链性能损耗比较大)

// 父类函数
function Human(name) {      
    this.name = name
}
Human.prototype.run = function () {
    console.log(this.name + "跑")
    return undefined
}

// 子类函数
function Man(name) {            
    Human.call(this, name)      // 把this传入Human父类函数,因此在父类函数内部的this就是此处的this
    this.gender = '男'
}

// Man.prototype.__proto__ = Human.__proto__  // Man的原型链原是直接指向Object,现插入一层Human

/**
    let object = new Man("x")
    object.__proto__ === Man.prototype
    object.__proto__.prototype === Human.prototype
    object.__proto__.__proto__.__proto__ === Object.prototype
    object.__proto__.__proto__.__proto__.__proto__ === null
*/

// 由于IE不支持直接操作__proto__,其中插入原型链也可以利用new实现:
var f = function () {}
f.prototype = Human.prototype
Man.prototype = new f()        

// 为子类添加方法
Man.prototype.fight = function () {
    console.log('攻击')
}

ES6实现

class Human{
    constructor(name) {
        this.name = name
    }
    run(){
        console.log(this.name + "跑")
        return undefined
    }
}
class Man extends Human {       // 表示在Man的原型链中插入Human
    constructor(name){
        super(name)
        this.gender = '男'
    }
    fight(){
        console.log('攻击')
    }
}

MIXIN

将一个对象的属性复制给另一个对象

let mixin = function(dest, src) {
    for(let key in src) {
        dest[key] = src[key]
    }
}

也可以使用Object.assign实现:

Object.assign(dest, src)

柯里化

把函数参数固定下来转化成偏函数:

let f = function(x, y) {
    return x + y
}
let g = function(y) {
    return f(1, y)
}
f(1, 2)
g(2)

也可以多次传参:

var cache = []
var add = function(n) {
    if (n === undefined) {
        return cache.reduce((p, n) => p + n, 0)
    }
    else {
        cache.push(n)
        return add
    }
}

add(1)(2)(3)...()

高阶函数

至少满足一个条件即为高阶函数

function add(x, y) {
    return x + y
}

f = Function.prototype.bind.call(add, undefined, 1)     // 把其中一个参数固定为1,也实现为柯里化
f(2)

Web性能优化

浏览器请求网页的过程及优化:

其他:

let liList = document.querySelectorAll('li')
liList[0].onclick = () => console.lot(1)
liList[1].onclick = () => console.lot(1)
liList[2].onclick = () => console.lot(1)

// 可以直接监听其父元素
ul.onclick = (e) => {
    if (e.target.tagName === "LI")
        console.log(1)
}
上一篇 下一篇

猜你喜欢

热点阅读