首页 > 技术文章 > 前端学习总结——JavaScript基础语法

nicolelhh 2021-01-09 22:30 原文

变量类型和计算

  • 值类型和引用类型
    • 值在栈中存储,i = 1,将 i 的值赋值给 j ,在栈中新开辟一块区域存储 j = 1,j 跟 i 是两个独立的值,各自占用一块内存空间;
    • 引用类型在堆中存储,a = {x: 20},a在堆中申请一个内存地址1,把值放在这个地址上,变量a在栈中保存,其值为内存地址1,这个地址指向{x: 20}这个对象,当把a赋值给b,实际上是把地址1赋值给b,通过b修改这个对象,a也会改变,也就是“传址”;
    • 值类型和引用类型的保存实际上是计算机一种性能优化的手段,几乎所有语言都是这样保存数据的,由于值类型比引用类型占用的内存小,赋一次值开辟一块内存来存储一个值类型的数据也还无妨。
    • 写个深拷贝的实现:
function deepClone (obj = {}) {
    if (typeof obj !== 'object' || obj == null){
        // 不是对象或者数组, 直接返回
        return obj;
    }
    let result;
    if (obj instanceof Array) {
        result = [];
    } else {
        result = {};
    }

    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            // 保证 key 不是原型的属性
            //递归
            result[key] = deepClone(obj[key]);
        }
    }
    return result;
}
  • 类型判断
    • typeof能判断哪些类型?
      • 数据类型分简单数据类型/复杂数据类型(原始类型/对象类型, 值类型/引用类型 ),简单数据类型有: undefined\number\string\Boolean\symbol,复杂数据类型:object\function\null\array\RegExp;
      • typeof 能够检测所有值类型, 识别函数, 判断是否是引用类型(object: array, obj, null), 不可再细分, 更具体的 array 等引用类型需要用到 instanceof 来进行检测;
      • 函数 function 是特殊的引用类型, 但不用于存储数据, 所以没有拷贝\复制函数一说。

原型和原型链

  • 原型:创建一个构造函数函数,它会按照规则创建一个prototype属性,这个属性指向原型对象,这个原型对象上有一个默认constructor属性指回这个构造函数。原型上的非手动添加的属性和方法都继承自上一级构造函数的原型,调用这个构造函数创建新的实例,这个实例上的__proto__指针就被赋值为构造函数的原型对象。

   实例与构造函数的原型之间有直接的联系,但是实例与构造函数之间没有。

  • 原型链:原型是有层级的,实例通过原型继承属性和方法。每个构造函数都有一个原型,原型上有一个属性指回构造函数,而实例上有一个内部指针__proto__指向原型。原型可能是另一个构造函数的实例,那么他也有一个指针__proto__指向它的构造函数的原型,这种关系可以是很多层的,这就在实例和原型之间形成一条原型链。

    原型链的顶端是object.prototype,其上定义的toString()、valueOf()、hasOwnProperty()。

    实例的隐式原型引用的是对应构造函数的显式原型:children.__proto__  === Parent.prototype

  • instanceof操作符:instanceof 返回一个布尔值,方法只要原型链上沾边就是true, [] instancemof Array/Object 返回true;
  •  手写jQuery考虑插件和扩展性代码
class jQuery {
    constructor(selector) {
        const result = document.querySelectorAll(selector);
        const length = result.length;
        for (let i = 0; i < length; i++) {
            this[i] = result[i];
        }
        this.length = length;
        this.selector = selector;
    }
    get(index) {
        return this[index];;
    }
    each(fn) {
        for (let i = 0; i < this.length; i++) {
            const elem = this[i];
            fn(elem)
        }
    }
    on(type, fn) {
        return this.each(elem => {
            elem.addEventListener(type, fn, false)
        })
    }
}
    // 自己写的jQuery插件
    jQuery.prototype.dialog = function (info) {
    alert(info);
}
// 造轮子
class myJQuery extends jQuery {
    constructor(selector) {
        super(selector);
    }
    // 扩展自己的方法
    addClass(className) {
        
    }
    style(data) {

    }
}

 

作用域和闭包

  • 自由变量:一个变量在当前作用域内没有被定义,但是被使用了,会向上层作用域逐级寻找,直到顶级作用域没找到报错:XX is not defined
  • 闭包:是作用域应用的特殊情况,函数作为参数被传递或者函数作为返回值返回,函数作为返回值的时候,函数定义在局部作用域,在全局作用域调用的时候,局部作用域内定义的自由变量,在定义的地方向上级逐级查找变量的值,而不是在全局作用域处调用的地方向上查找自由变量的值;函数作为参数被传递的时候,函数定义在全局作用域,在局部作为参数被调用,还是在定义的地方去向上级查找自由变量的值。
  • 利用闭包隐藏数据, 做一个简单的cache工具
function createCache() {
    const data = {}; // 闭包中的数据, 被隐藏, 不被外界访问
    return {
        set: function(key, val) {
            data[key] = val;
        },
        get: function(key) {
            return data[key];
        }
    }
}

const c = createCache();
c.set('a', 100);
console.log(c.get('a'));

 

  • this指向:this的指向是由调用的时候决定的,不是由定义的时候决定的:
    • 在普通函数中:window
    • 在对象方法中被调用:指向对象本身
    • 通过call、apply、bind:根据传入的值来决定
    • 在class中:当前实例本身
    • 在箭头函数中:箭头函数没有this,需要向上级作用域的this寻找
  • 写一个bind方法:
Function.prototype.bind1 = function () {
    // **将参数列表拆解为数组
    const args = Array.prototype.slice.call(arguments); 
    // 获取this ( 数组第一项 )
    const this1 = args.shift();
    // fn1.bind( ... )中的 fn1
    const self = this;
    // 返回一个函数
    return function() {
        return self.apply(this1, args);
    };
}

 

  • 写一个call方法:
Function.prototype.myCall = function (ctx, ...args) {
            if (typeof this !== 'function') {
                throw new Error('not a function')
            }
            let fn = Symbol();
            ctx = ctx || window;
            ctx[fn] = this;
            ctx[fn](...args);
            delete ctx[fn];
        }
        function fn1(a, b) {
            console.log(this.name);
            console.log(a, b);
        }

 

  • 写一个apply方法:
 Function.prototype.myApply = function (ctx, args = []) {
            if (typeof this !== 'function') {
                throw new Error('not a function')
            }
            let fn = Symbol();
            ctx = ctx || window;
            if(!(args instanceof Array)){
                throw new TypeError('请输入数组作为参数');
            }
            ctx[fn] = this;
            ctx[fn](...args);
            delete ctx[fn];
        }

 

异步

  • 如何理解JS是单线程语言?
    • JS主线程在同一时间只能执行一个任务,而像网络请求、定时器等需要耗时的任务,我们浏览器遇到这些不能等待,会将耗时的任务放在队列中,等待主线程的任务执行完,再执行。这种异步的方式不阻塞代码的执行。
  • 什么是异步编程?
    • JS是单线程语言,主线程只能同一时间执行一个任务,待主线程执行完再去任务队列里面轮询任务,在执行任务期间,中间进来的任务都是排列在任务队列里。代码间有相互依赖关系,要求任务队列按照一定的次序排列,大量会用到嵌套来解决这个问题,但嵌套会增加代码复杂性,甚至产生“回调地狱”。ES6的promise语法是一种异步编程机制,解决大量的回调的问题。
  • Promise:
    • 类型:Function
    • 参数:执行器函数,一般为两个函数参数,用于控制期约的状态转换。
    • 期约的状态:
      • pending:可以转化为兑现或者拒绝,一旦落定不可更改;
      • fulfilled/resolved
      • rejected
    • 传入的参数为一个promise对象,其兑现和拒绝的状态会被保留 ,向后传递,期约状态改变,不会再被重设,不可逆不可撤销;
    • then返回的也是一个promise,链式,成对的promise-then关系,下个then处理上一个返回的promise;
    • then/catch默认返回的是解决状态的promise
    • 最后放catch,统一对前面的错误进行处理
    • 用promise实现图片加载的函数:
function ajax(url) {
    const p = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('get', url, true);
        xhr.onreadystatechange = function () {
            if(xhr.readyState === 4) {
                if(xhr.status === 200) {
                    resolve(
                        JSON.parse(xhr.responseText)
                    )
                } else if (xhr.status === 404) {
                    reject(new Error('404 not found'))
                }
            }
        }
        xhr.send(null);
    })
    return p;
}

 

推荐阅读