• javascript基础的查缺补漏


    对象转基本类型

    let a = {
      valueOf() {
        return 0;
      },
      toString() {
        return '1';
      },
      [Symbol.toPrimitive]() {
        return 2;
      }
    }
    1 + a // => 3
    '1' + a // => '12'
    

    优先级: Symbol.toPrimitive>valueOf>toString

    'a'++'b'
    因为+'b' 会被强制转换成NaN
    

    function Foo() {
        return this;
    }
    Foo.getName = function () {
        console.log('1');
    };
    Foo.prototype.getName = function () {
        console.log('2');
    };
    
    new Foo.getName();   // -> 1
    new Foo().getName(); // -> 2
    优先级的顺序
    new (Foo.getName());
    (new Foo()).getName();
    //new Foo() 是返回return的内容而不是去看构造函数的属性	
    

    如何成为全核工程师

    先精后广,一专多长

    []false, !![]true

    js中隐式强制类型转换
    转化为数字类型  false等于0, true等于1
    如果是对象,先调用valueOf,如果没有用调用toString,这个过程叫ToPrimitive()
    NaN==NaN  false
    首先分析[]==![]
    Boolean() 判断 0、-0、null、false、NaN、undefined、或者空字符串(""),则生成的 Boolean 对象的值为 false,
    ![] 变成!Boolean([]) 也就是!true,也就是false
    

    [] + {} 和 {} + []一样吗

    当有一个操作数是字符串时

    • 如果两个操作数都是字符串,将两个字符串拼接起来
    • 如果只有一个操作符石字符串,则另一个操作数转换为字符串(toString)

    当有一个操作数是复杂数据类型(对象,数组)时,将两个操作数都转换为字符串(toString)相加

    有一个操作数是简单数据类型(true/false, null,undefined) 时,同时不存在复杂数据类型和字符串,则将两个操作数都转换成数值(ToNumber)相加。

    []+{}

    满足a有对象/数组,所有都转换为字符串拼接
    ""+"[object object]"="[object Object]"
    

    1+{}

    满足第三条 a是对象/数组
    "1[object Object]"
    

    {}+[]

    "[object Object]"+toNumber([])
    "[object Object]"+0
    

    {}+{}

    "[object Object][object Object]"
    

    函数的name属性

    通过构造函数方式创建的函数name都是 'anonymous'
    
    使用bind方法得到一个新的函数
    
    function ff(){};
    var f=ff.bind();
    f.name 是bound ff
    

    闭包

    作用域是定义时的作用域,而不是执行时的作用域

    闭包的使用场景

    函数作为返回值

    function F1(){
    	var a = 100
    	//返回一个函数(函数作为返回值)
    	return function(){
    		console.log(a)
    	}
    }
    

    函数作为参数传递

    function F1(){
    	var a = 100
    	return function(){
    		console.log(a)   //自由变量,父作用域寻找
    	}
    }
    

    一个函数执行如果形成一个不销毁作用域,保护里面的私有变量或者存储私有变量,但是闭包容易引起内存泄露

    造成内存泄露的情况:

    • 全局变量
    • 不销毁作用域
    function fn(){
        var a=1;
        return function(){
            a++
        }
    }
    

    作用域

    全局:关闭浏览器的时候销毁
    私有: 看是否返回地址并且被占用了,决定是否销毁
    var 是没有块级作用域的
    

    作用域链

    是一个查找过程: 在一个私有作用域下遇到变量了,先看是不是自己私有的,如果不是往上一级找,没有继续找一只找到window下为之,没有就报错
    

    上一级作用域

    看当前整个作用域对应我的地址是在哪一个作用域下定义的,那个作用域就是当前这个作用域的上一级
    

    块级作用域

    {}   if(){}    for(){}   while(){}
    

    let 和const 定义的变量属于一个私有作用域,变量是私有变量

    实例即可以通过构造函数中this.属性的方式得到私有属性还可以通过__proto__拿到所属类的原型的公有属性

    词法作用域

    常见的变量
    

    javaScript 引擎

    javaScript引擎是谷歌的v8引擎,这个引擎由两个部分组成

    • 内存堆:这是内存分配发生的地方
    • 调用栈:这是你的代码执行的地方

    创建执行上下文有两个阶段

    • 创建阶段
    • 执行阶段

    在创建阶段会发生三件事

    • this值的决定,就是this绑定
    • 创建词法环境组件
    • 创建变量组件

    this的绑定

    在全局执行上下文中,this的值指向全局对象,(在浏览器中,this引用window对象)

    在函数执行上下文,this的值取决于该函数式如何被调用的,如果它被一个引用对象调用,那么this会被设置成那个对象,否则this的值被设置全局对象或者undefined(在严格模式下)

    词法环境内部有两个组件

    • 环境记录器
    • 一个外部环境的引用

    环境记录器是存储变量和函数声明的实际位置

    外部环境的引用意味着他可以访问其父级词法环境(作用域)

    词法环境有两种类型

    全局环境(在全局执行上下文中)是没有外部环境引用的词法环境,

    函数环境中,函数内部用户定义的变量存储在环境记录器中,并且引用的外部环境可能是全局环境,或者任何包含此内部函数的外部函数。

    简而言之

    • 在全局环境中,环境记录器是对象环境记录器
    • 在函数环境中,环境记录器是声明式环境记录器

    值类型

    // 值类型:Number、string、bollean、undefined
    var a = 100
    var b = a
    a = 200
    console.log(b) // 100 保存与复制的是值本身
    typeof abc      //"undefined"
    typeof null    //"object"
    为什么要说呢?因为我错了好多次
    typeof区分不了引用类型(除了函数)
    用instanceof 来区分引用类型
    alert(person instanceof Object); // 变量 person 是 Object 吗?
    
    alert(colors instanceof Array); // 变量 colors 是 Array 吗?
    
    alert(pattern instanceof RegExp); // 变量 pattern 是 RegExp 吗?
    

    引用传值

    值传递和引用传递

    function addNum(num)
    { 
     num+=10; 
     return num; 
    } 
    var num=10; 
    var result=addNum(num); 
    console.log(num);//10
    console.log(result);//20
    当为函数传递参数的时候,是将此值复制一份传递给函数,所以在函数执行之后,num本身的值并没有改变,函数中的被改变的值仅仅是副本而已
    
    function mutate(obj) {
      obj.a = true;
    }
    
    const obj = {a: false};
    mutate(obj)
    console.log(obj.a); // 输出 true
    
    在值传递的场景中,函数的形参只是实参的一个副本(相当于a拷贝了一份),当函数调用完成后,并不改变实参
    在引用传递的场景中,函数的形参和实参指向同一个对象,当参数内部改变形参的时候,函数外面的实参也被改变
    
    function setName(obj){
    	obj.name = '嘉明';
    	obj = new Object();
    	obj.name = '庞嘉明';
    }
    var person = new Object();
    setName(person);
    console.log(person.name); // '嘉明',为啥不是'庞嘉明'呢?
    新建的对象 obj = new Object()
    它自己空间保存的地址将会被新的对象的存储地址所覆盖,因为是传值不是引用,所以它不会影响到student空间所保存的地址,故最后虽然对obj的name属性重新赋值,但也丝毫不影响之前赋值结果,按值传递
    你传进的一个对象 对象在内存地址中,即使你再外部赋值,但是内部 改变了,你外部就等于没有赋值
    

    当取值为百分比时,需要特别注意:百分比不是相对于父元素的高度的,而是相对于父元素的宽度的heighttop的百分比取值,总是相对于父元素的高度

    padding-topmargin-toppadding-bottommargin-bottom取值为百分比时,是相对于父元素的宽度

    fixed问题

    一提到position:fixed,自然而然就会想到:相对于浏览器窗口进行定位。
    
    但其实这是不准确的。如果说父元素设置了transform,那么设置了position:fixed的元素将相对于父元素定位,否则,相对于浏览器窗口进行定位。
    

    JavaScript 的怪癖

    隐式转换

    隐式转换为Boolean

    if语句

    字符串的隐式转换

    加运算符(+) ,但是只要其中一个操作数是字符串,那么它执行连接字符串的操作

    为什么 ++[[]][+[]]+[+[]] = 10?

    拆分
    ++[[]][+[]]
    +
    [+[]]
    继续拆分
    
    ++[[]][0]
    +
    [0]
    
    继续拆分
    
    +([] + 1)
    +
    [0]
    
    +([] + 1) === +("” + 1),并且 
    +("” + 1) === +("1"),并且 
    +("1") === 1 
    
    最后简化为
    1+[0]
    []==''  [0]=='0', [1]=='1'
    所以 1+'0' ==='10'
    

    2==true为什么是false

    因为是比较值类型是否相等,true转换为数字是1 ,所以21为false

    '2'true '2' 隐式转化为2 2true 为false

    null >=0 ? true:false

    null == 0  // false  null在设计上,在此处不尝试转型. 所以 结果为false. 
    null > 0  // false   null 尝试转型为number , 则为0 . 所以结果为 false, 
    null >= 0  // true 
    null<=0		//true
    那么你看
    -null == 0  // true
    +null == 0 // true
    Number(null) == 0  // true
    

    你不知道的JavaScript续集

    数组

    使用delete运算符可以将单元从数组中删除,但是请注意,单元删除后,数组的length属性并不会发生变化。
    数组通过数字进行索引,但有趣的是它们也是对象,所以也可以包含字符串键值和属性(但这些并不计算在数组长度内)

    var a = [ ];
    a[0] = 1;
    a["foobar"] = 2;
    a.length; // 1
    a["foobar"]; // 2
    a.foobar; // 2
    如果字符串键值能够被强制类型转换为十进制数字的话,它就会被当做数字索引来处理
    var a = [ ];
    a["13"] = 42;
    a.length; // 14
    
    类数组
    var a = { '0': 1, '1': 2, '2': 3, length: 3 };
    function foo() {
        var arr = Array.prototype.slice.call(arguments);
        arr.push("bam");
        console.log(arr);
    }
    foo("bar", "baz"); // ["bar","baz","bam"]
    同时用Array.from() 也能实现同样的功能
    对伪数组或可迭代对象(包括arguments Array,Map,Set,String…)转换成数组对象
    

    字符串

    JavaScript中字符串是不可变的,而数组是可变的。

    特殊的数字

    var a=2/'foo';
    a==NaN //false 
    因为NaN===NaN  //false
    可以使用全局isNaN() 来判断一个值是否是NaN
    但是 isNaN('abc')  //true
    ES6开始我们使用Number.isNaN()
    var a = 2 / "foo";
    var b = "foo";
    Number.isNaN( a ); // true
    Number.isNaN( b ); // false——好!
    
    无穷数
    var a = 1 / 0; // Infinity
    var b = -1 / 0; // -Infinity
    
    零值    常规的0(也叫+0)和-0
    var a = 0 / -3; // -0
    var b = 0 * -3; // -0
    从字符串转换为数字
    +"-0"; // -0
    Number( "-0" ); // -0
    JSON.parse( "-0" ); // -0
    我们为什么需要负零呢
    数字的符号位用来代表其他信息(比如移动的方向)
    此时如果一个值为0的变量失去了它的符号位,它的方向信息就会丢失。
    

    值和引用

    var a = [1,2,3];
    var b = a;
    a; // [1,2,3]
    b; // [1,2,3]
    // 然后
    b = [4,5,6];
    a; // [1,2,3]
    b; // [4,5,6]
    由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向
    
    function foo(x) {
        x.push(4);
        x; // [1,2,3,4]
        // 然后
        x = [4, 5, 6];
        x.push(7);
        x; // [4,5,6,7]
    }
    var a = [1, 2, 3];
    foo(a);
    a; // 是[1,2,3,4],不是[4,5,6,7]
    我们无法自行决定使用值复制还是引用复制,一切由值得类型来决定。
    如果通过值复制的方式来传递复合值(如数组),就需要为其创建一个复本,这样传递的就不再是原始值
    foo(a.slice())
    
    如果要将标量基本类型值传递到函数内并进行更改,就需要将该值封装到一个复合值(对象、数组等)中,然后通过引用复制的方式传递。
    function foo(wrapper) {
        wrapper.a = 42;
    }
    var obj = {
        a: 2
    };
    // var obj=new Object();  obj.a=2;
    foo(obj);
    obj.a; // 42
    与预期不同的是,虽然传递的是指向数字对象的引用复本,但我们并不能通过它来更改其中的基本类型值
    
    function foo(x) {
        x = x + 1;
        x; // 3
    }
    var a = 2;
    var b = new Number(a); // Object(a)也一样
    foo(b);
    console.log(b); // 是2,不是3
    只是多数情况下我们应该优先考虑使用标量基本类型
    

    封装对象包装

    var a=Boolean(false);
    var b=new Boolean(false);
    if (!b) {
    console.log( "Oops" ); // 执行不到这里
    }
    我们为false创建了一个封装对象,然而该对象是真值,所以这里使用封装对象得到的结果和使用false截然相反
    如果想要自行封装基本类型值,可以使用Object(...)函数(不带new关键字)
    var a = "abc";
    var b = new String( a );
    var c = Object( a );
    typeof a; // "string"
    typeof b; // "object"
    typeof c; // "object"
    b instanceof String; // true
    c instanceof String; // true
    

    拆封

    可以使用ValueOf()函数
    var a = new String( "abc" );
    var b = new Number( 42 );
    var c = new Boolean( true );
    a.valueOf(); // "abc"
    b.valueOf(); // 42
    c.valueOf(); // true
    
    使用隐式拆封
    var a = new String( "abc" );
    var b = a + ""; // b的值为"abc"
    typeof a; // "object"
    typeof b; // "string"
    

    原生函数作为构造函数

    关于数组(array)、对象(object)、函数(function)和正则表达式,我们通常喜欢以常量的形式来创建它们。实际上,使用常量和使用构造函数的效果是一样的(创建的值都是通过封装对象来包装)
    
    var a = new Array( 1, 2, 3 );
    a; // [1, 2, 3]
    var b = [1, 2, 3];
    b; // [1, 2, 3]
    构造函数Array(..)不要求必须带new关键字。不带时,它会被自动不上。因此Array(1,2,3)和new Array(1,2,3)的效果是一样的。
    Array构造函数只带一个数字参数的时候,该参数会被作为数组的预设长度(length),而非只充当数组中的一个元素
    
    

    表达式的副作用

    var a = 42;
    var b = a++;
    a; // 43
    b; // 42 这是a++的副作用
    
    ========================
    function foo() {
        a = a + 1;
    }
    var a = 1;
    foo(); // 结果值:undefined。副作用:a的值被改变
    

    你懂 JavaScript 嗎?

    toJSON

    var obj = {
        key: 'foo',
        toJSON: function () {
            return 'bar';
        }
    };
    var ret = JSON.stringify(obj);
    console.log(ret);
    区别非常明显,toJSON 的返回值直接代替了当前对象
    

    Number

    undefined   ---> NaN
    null		---> 0
    boolean 的true为1  false即是0
    string  -->数字或NaN
    object		若定义valueOf优先用,其次toString
    
    Number(undefined) // NaN
    Number(null) // 0
    Number(true) // 1
    Number(false) // 0
    Number('12345') // 12345
    Number('Hello World') // NaN
    Number({ name: 'Jack' }}) // NaN
    
    const a = {
      name: 'Apple',
      valueOf: function() {
        return '999'
      }
    }
    
    Number(a) // 999
    
    const a = new String('');
    const b = new Number(0);
    const c = new Boolean(false);
    
    !!a // true
    !!b // true
    !!c // true
    

    parseInt

    参数:第一个参数是string 第二个参数是介于2和36之间的整数,通常默认为10,也就是我们通常使用的十进制转换,如果是5就是5进制,超出这个范围,则返回NaN。如果第二个参数是0、undefined和null,则直接忽略
    * 将字符串转为整数
    * 如果字符串头部有空格,空格会被自动去除
    * 如果参数不是字符串,先转为字符串再转换
    parseInt('12px')  如果遇到不能转为数字的字符,就不再进行下去,返回转好的部分
    如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN。
    如果开头是0x按照16进制转换,如果是0按照10进制转换
    

    又犯错了一次

    const a = true;
    const b = 123;
    
    a === b // false
    a == b // false
    true强制转换为1
    
    const a = '1,2,3';
    const b = [1,2,3];
    a === b // false
    a == b // true
    在a == b当中,阵列a由于没有valueOf(),只好使用toString()取得其基型值而得到字串'1,2,3',此时就可比较'1,2,3' == '1,2,3',因此是相等的(true)。
    
    Object(null) 和Object(undefined) 等同于Object()也就是{}
    var a = null;
    var b = Object(a); // 等同於 Object()
    a == b; // false
    
    var c = undefined;
    var d = Object(c); // 等同於 Object()
    c == d; // false
    
    var e = NaN;
    var f = Object(e); // 等同於 new Number(e)
    e == f;//false
    
    避免修改原型的valueOf
    Number.prototype.valueOf = function() {
      return 3;
    };
    
    new Number(2) == 3; // true
    

    抽象的关系运算符

    • a <= b其实是!(b > a),因此!false得到true。
    • a >= b其实是b <= a也就是!(a > b)等同于!false得到true
    const a = { b: 12 };
    const b = { b: 13 };
    
    a < b // false,'[object Object]' < '[object Object]'
    a > b // false,其實是比較 b < a,即 '[object Object]' < '[object Object]'
    a == b // false,其實是比較兩物件的 reference
    
    a >= b // true
    a <= b // true
    
    `[]==[]`  false 因为两个的地址不是一样的
    
    `'ab' < 'cd' // true `  以字典的字母顺序形式进行比较
    
    'Hello World' > 1 // false,字串 'Hello World' 无法转化为数字,变成了NaN
    NaN 不大于、不小于、不等于任何值,当然也不等于自己
    

    图解构造器Function和Object的关系

    //①构造器Function的构造器是它自身
    Function.constructor=== Function;//true
    
    //②构造器Object的构造器是Function(由此可知所有构造器的constructor都指向Function)
    Object.constructor === Function;//true
    
    //③构造器Function的__proto__是一个特殊的匿名函数function() {}
    console.log(Function.__proto__);//function() {}
    
    //④这个特殊的匿名函数的__proto__指向Object的prototype原型。
    Function.__proto__.__proto__ === Object.prototype//true
    
    //⑤Object的__proto__指向Function的prototype,也就是上面③中所述的特殊匿名函数
    Object.__proto__ === Function.prototype;//true
    Function.prototype === Function.__proto__;//true
    

    Function instanceof Object 和 Object instanceof Function 运算的结果当然都是true啦

    所有的构造器的constructor 都指向Function

    Function 和prototype指向一个特殊匿名函数,而这个特殊匿名函数的 __proto__ 指向 Object.prototype

    Array.of

    Array.of方法用于将一组值,转换为数组。
    Array.from()
    Array.from方法用于将两类对象转为真正的数组:类似数组的对象和可遍历(iterator)的对象(包括Map和Set)
    

    创建包含N个空对象的数组

    Array(3).fill().map(()=>({}))
    Array.apply(null,{length:3}).map(()=>({}))
    

    函数表达式

    函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解析。
    console.log(sum(10 , 10)); //TypeError: sum is not a function
    var sum = function(num1 , num2){
    	return num1 + num2;
    }
    

    Javascript中Y组合子

    递归就是函数不断调用自身
    阶乘
    let factorial=n=>n?factorial(n-1)*n:1;
    
    const factorial = n => n === 1 ? 1 : n * factorial(n - 1)
    优化
    尾递归: 调用自身函数,计算仅用常量栈空间
    const factorial = (n, total) => n === 1 ? total : factorial(n - 1, n * total)
    优化
    柯里化,将尾递归变为只接受单个参数的变量
    const fact = (n, total = 1) => n === 1 ? total : fact(n - 1, n * total)
    
    Lambda函数(匿名函数)
    // ES5
    var f = function (x) {
      return x;
    };
    
    // ES6
    const f = x => x
    lambda表达式写出递归(匿名函数递归)
    将lambda表达式作为参数之一传入其身
    const factorial= (f,n) => n===1 ? 1 : n*f(f,n-1);
    factorial(factorial,6)
    //这个也太难看了,解决方案柯里化
    // 这块不怎么好懂我就忽略了
    
    Lambda演算
    Lambda演算中所有函数式匿名的,它们没有名称,只接受一个输出变量,即独参函数
    构建一个高阶函数,它接受一个函数作为参数,并让这个函数将自身作为参数调用其自身:
    const invokeWithSelf = f => f(f)
    写个递归
    const fact = (n, total = 1) => n === 1 ? total : fact(n - 1, n * total)
    拿到前面的进行优化
    const fact = f => (total = 1) => n => n === 1 ? total : f(f)(n * total)(n - 1)
    const factorial = fact(fact)()
    
    factorial(6) // => 720
    构建Y
    const fact = f => (total = 1) => n => n === 1 ? total : f(n * total)(n - 1)
    
    const Y = f => (x => f(v => x(x)(v)))
                   (x => f(v => x(x)(v))) // 瞧,这不就是黑魔法Y组合子嘛
    
    const factorial = Y(fact)()
    
    factorial(6) // => 720
    

    尾调用优化

    尾调用时指在函数return的时候调用一个新的函数,由于尾调用的实现需要存储到内存中,在一个循环体中,如果存在函数的尾调用,你的内存可能爆满或溢出。
    尾调用实际用途——递归函数优化
    在ES5时代,我们不推荐使用递归,因为递归会影响性能。
    
    但是有了尾调用优化之后,递归函数的性能有了提升。
    const factorial = (n, total) => n === 1 ? total : factorial(n - 1, n * total)
    

    let

    let和const都能够声明块级作用域,let的特点是不会变量提升,而是被锁在当前块中。
     function test() {
            if(true) {
              console.log(a)//TDZ,俗称临时死区,用来描述变量不提升的现象
              let a = 1
            }
        }
        test()  // a is not defined
    
        function test() {
            if(true) {
              let a = 1
            }
            console.log(a)
        }    
        test() // a is not defined
        
     临时死区的意思是在当前作用域的块内,在声明变量前的区域叫做临时死区。   
    

    Object.is()

    用来解决JavaScript中特殊类型 == 或者 === 异常的情况。
    Object.is()来处理2个值的比较。
        console.log(Object.is(NaN, NaN)) // true
        console.log(Object.is(+0, -0)) // false
        console.log(Object.is(5, "5")) //false
    

    解构赋值

    function test(value) {
        console.log(value);
    }
    test({a=1,b=2}={a:2,b:3});
    

    yield使用限制

    yield只可以在生成器函数内部使用,如果在非生成器函数内部使用,则会报错。
       function *createIterator(items) {
            //你应该在这里使用yield
          items.map((value, key) => {
            yield value //语法错误,在map的回调函数里面使用了yield
          })
        }
        const a = createIterator([1, 2, 3]);
        console.log(a.next()); //无输出
        
    在对象中添加生成器函数
        const obj = {
          a: 1,
          *createIterator() {
            yield this.a
          }
        }
        const a = obj.createIterator();
        console.log(a.next());  //{value: 1, done: false}
    

    函数的caller

    caller : 当前这个函数在哪个函数调用的
    function fn(){
        console.log(fn.caller);
    }
    function ff() {
        fn();
    }
    ff();//[Function: ff]
    arguments.callee  就是当前函数本身
    function fn(){
        console.log(argument.callee) 
    }
    fn.prototype.constructor===fn;//true  ,也代表的是函数本身
    

    捕获和冒泡

    xxx.onclick=function(){} //DOM0事件绑定,给元素的事件行为绑定方法,这些方法在事件传播的冒泡阶段(或者目标阶段)执行的
    xxx.addEventListener('xxx',function(){},false)
    //第三个参数false也是控制绑定的方法在事件传播的冒泡阶段执行,但是在捕获阶段执行没有实际意义,默认是false,可以不写
    

    DOM0和DOM2的运行机制

    DOM0事件绑定的原理:就是给元素的某一个事件私有属性赋值(浏览器会建立监听机制,当我们出发元素的某个行为,浏览器会自己把属性中赋的值去执行)

    DOM0事件绑定:只允许给当前元素的某个事件行为绑定一个方法,多次绑定后面的内容会替换前面绑定的,以最后一次绑定的方法为主

    DOM0事件绑定和DOM2事件绑定的区别

    机制不一样

    • DOM0采用的是私有属性赋值,所有只能绑定一个方法
    • DOM2采用的是事件池机制,所以能绑定多次方法

    移出的操作

        let list = document.querySelector('#list');
        list.addEventListener('click',function (ev) {
            console.log(ev.target.innerHTML);
        })
        list.addEventListener('click',function () {
            console.log(2);
        })
    
        box.onclick=function(){}
        box.onclick=null// DOM0的移出(不需要考虑绑定的是谁)
    
    //DOM2移出的时候
        function f3() {
            console.log(2);
        }
        list.addEventListener('click',f3);
        list.removeEventListener('click',f3);
            //DOM2移出的时候,必要清除移出的是哪个方法技巧(不要绑定匿名函数,都绑定实名函数)
    

    DOM0和DOM2是可以同时使用,因为是浏览器的两个运行机制,执行顺序和编写顺序有关

    mouseenter和mouseover的区别

    1. over属于滑过事件,从父元素进入子元素,属性离开父亲,会触发父元素的out,触发子元素的over
    enter属于进入,从父元素进入子元素,并不算离开父元素,不会触发父元素的leave,触发子元素的enter
    2. enter和leave阻止了事件的冒泡传播,而over和out还存在冒泡传播的
    
    所有对于父元素嵌套子元素的这种情况,我们用enter的使用会比over多一些
    

    事件委托(事件代理)

    给容器的click绑定一个方法,通过事件的冒泡传播机制,把容器的click行为触发,根据事件对象中的事件源(ev.target)来做不同业务处理

    <ul id="list">
        <li>item 1</li>
        <li>item 2</li>
        <li>item 3</li>
        <li>item n</li>
    </ul>
    <script>
        let list = document.querySelector('#list');
        list.onclick=function (ev) {
            let target=ev.target||window.event.target;
            console.log(target.innerHTML);
        }
    </script>
    

    JQ的事件绑定

    on/off : 基于DOM2事件绑定实现事件的绑定和移除

    one:只绑定一次,第一次执行完成后,会把绑定的方法移出(基于on/off完成)

    click/ mouseenter/... jq提供的快捷绑定方法,但是这些方法都是基于on/off完成的

    delegate 事件委托方法(在1.7以前用的是live方法)

    $(document).on('click',fn)
    $(document).off('click',fn)
    $(document).one('click',fn)
    $(document).click(fn)
    

    自执行匿名函数

    任何消除函数声明和函数表达式间歧义的方法,都可以被解析器正确识别

    针对这些一元运算符,到底用哪个好呢,测试发现()的性能最优越

    (function(){ /* code */ }());
    !function(){alert('iifksp')}()        // true
    +function(){alert('iifksp')}()        // NaN
    -function(){alert('iifksp')}()        // NaN
    ~function(){alert('iifksp')}()        // -1
    

    发布订阅设计模式(观察者模式)

    思想:准备一个容器,把到达指定时候要处理的事情,事先一一增加到容器中(发布计划,并且向计划表中订阅方法),当到达指定时间点,通知容器中的方法依次执行

    文章

    forEach和map的区别

    相同点

    • forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组)

    map

    返回一个新数组,不会对空数组进行检测,不会该变原有数组

    forEach

    让数组每一项做一件事

    空数组就不会执行回调函数

    Promise A+规范

    class Promise {
        constructor(excutorCallBack) {
            this.status = 'pending';
            this.value = undefined;
            this.fulfilledAry = [];
            this.rejectedAry = [];
            let resolveFn = result => {
                let timer=setTimeout(()=>{
                    clearTimeout(timer);
                    if (this.status !== 'pending') return;
                    this.status = 'fulfilled';
                    this.value = result;
                    this.fulfilledAry.forEach(item=>item(this.value))
                })
            };
            let rejectFn = reason => {
                let timer=setTimeout(()=>{
                    if (this.status !== 'pending') return;
                    this.status = 'rejected';
                    this.value = reason;
                    this.rejectedAry.forEach(item => item(this.value));
                })
            };
            try{
                excutorCallBack(resolveFn, rejectFn());
            }catch(err){
                // 有异常信息按照rejected状态处理
                rejectFn(err);
            }
            excutorCallBack(resolveFn, rejectFn);
        }
    
        then(fulfilledCallBack, rejectedCallBack) {
            //处理不传递的状况
            typeof fulfilledCallBack!=='function'?fulfilledCallBack=result=>result:null;
            typeof rejectedCallBack!=='function'?rejectedCallBack=reason=>{
                throw new Error(reson.message);
            }:null;
            //返回一个新的promise实例
            return new Promise((resolve,reject)=>{
                this.fulfilledAry.push(()=>{
                    try{
                        let x=fulfilledCallBack(this.value);
                        x instanceof Promise?x.then(resolve,reject):resolve(x);
                        // if(x instanceof Promise){
                        //     x.then(resolve, reject);
                        //     return;
                        // }
                        // resolve(x);
                    }catch(err){
                        reject(err)
                    }
                });
                this.rejectedAry.push(()=>{
                    try{
                        let x=rejectedCallBack(this.value);
                        x instanceof Promise?x.then(resolve,reject):resolve(x);
                        // resolve(x);
                    }catch(err){
                        reject(err)
                    }
                });
            });
            // this.fulfilledAry.push(fulfilledCallBack);
            // this.rejectedAry.push(rejectedCallBack);
        }
    
        catch(rejectedCallBack) {
            return this.then(null,rejectedCallBack)
        }
    
        static all(promiseAry=[]){
            return new Promise((resolve, reject)=>{
                //index:记录成功的数量 result记录成功的结果
                let index=0,
                    result=[];
                for (let i = 0; i <promiseAry.length; i++) {
                    //promiseAry[i] 每一个需要处理的promise实例
                    promiseAry[i].then(val=>{
                        index++;
                        result[i]=val;
                        //索引需要和promiseAry对应,保证结果的顺序和数组的顺序一致
                        if (index === promiseAry.length) {
                            resolve(result);
                        }
                    }, reject);
                }
            });
        }
    }
    module.exports = Promise;
    

    call,apply,bind串联起来理解

    cat.call(dog, a, b) = cat.apply(dog, [a, b]) = (cat.bind(dog, a, b))() = dog.cat(a, b)
    

    本地存储和服务器存储

    用到本地存储的地方:

    • 页面之间的信息通信
    • 性能优化

    session和cookie

    session是服务器存储

    • 不兼容IE8及以下
    • 也有存储的大小限制,一个源下最多只能存储5MB内容
    • 本地永久存储,只要你不手动删除,永久存储在本地(但是我们可以基于API removeItem/clear手动清除)
    • 杀毒软件或者浏览器的垃圾清除暂时不会清除localStorage(新版本谷歌会清除localStorage)
    • 在隐私或者无痕浏览下,是记录localStorage
    • localStorage和服务器没有半毛钱关系

    cookie是客户端存储

    • 兼容所有的浏览器
    • 有存储的大小限制,一般一个源只能存储4kb内容
    • cookie有过期时间(当前我们自己可以手动设置这个时间)
    • 杀毒软件或者浏览器的垃圾清理都可能会把cookie信息强制掉
    • 在隐私或者无痕浏览器模式下,是不记录cookie的
    • cookie不是严格的本地存储,因为要和服务器之间来回传输
    localStorage.gsetItem([key],[value])//[value]必须是字符串格式(即使写的不是字符串,也会默认转换为字符串)
    localStorage.getItem([key]) //通过属性名获取存储的信息
    localStorage.removeItem([key])//删除指定的存储信息
    localStorage.clear()//清除当前域下存储的所有信息
    localStorage.key(0)//基于索引获取指定的key名
    

    设置cookie

    数组的方法

    flex

    文档

    需要一个容器 display:flex
    flex-direction (元素排列方向)
        row, row-reverse, column, column-reverse
    flex-wrap (换行)
        nowrap, wrap, wrap-reverse
    flex-flow (以上两者的简写)
        flex-direction || flex-wrap
    justify-content (水平对齐方式)
        flex-start, flex-end, center, space-between, space-around
    align-items (垂直对齐方式)
        stretch, flex-start, flex-end, center, baseline
    align-content (多行垂直对齐方式)
        stretch, flex-start, flex-end, center, space-between, space-around
    

    递归的本质是栈的读取

    在算法中我们会遇到很多递归实现的案例,所有的递归都可以转换成非递归实现,

    其转换的本质是:递归是解析器(引擎)来帮我们做了栈的存取,非递归是手动创建栈来模拟栈的存取过程

    递归组件可以转换成扁平数组来实现:

    更改DOM结构成平级结构,点击节点以及节点的视觉样式通过操作总的list数据区实现

    然后使用虚拟长列表来控制vue组组建实例创建的数量

    性能优化

    减少DNS查找,避免重定向

    DNS:负责将域名URL转化为服务器主机IP
    DNS查找流程:首先查看浏览器缓存是否存在,不存在则访问本机DNS缓存,再不存在则访问本地DNS服务器。所以DNS也是开销,通常浏览器查找一个给定URL的IP地址要花费20-120ms,在DNS查找完成前,浏览器不能从host那里下载任何东西。 
    当客户端的DNS缓存为空时,DNS查找的数量与WEB页面中唯一主机名的数量相等,所以减少唯一主机名的数量就可以减少DNS查找的数量
    

    资源async defer

    defer

    如果script设置了该属性,则浏览器会异步的下载该文件,并且不会影响后续DOM的渲染

    如果有多个设置了deferscript标签存在,则会按照顺序执行所有的scriptdefer脚本会在文档渲染完毕后,DOMContentLoaded事件调用前执行。

    <script defer src='1.js'></script>
    <script>
        window.addEventListener('DOMContentLoader',function(){
        console.log('DOMContentLoader')
    })
     </script>
    

    async

    async的设置,会使得script脚本异步的加载并在允许的情况下执行 async的执行,

    并不会按着script在页面中的顺序来执行,而是谁先加载完谁执行。

    推荐使用场景

    defer 如果你的脚本代码依赖于页面中的DOM元素(文档是否加载解析完毕),或者被其他脚本文件依赖

    • 评论框 代码语法高亮

    页面静态直出

    • 就是浏览器直接输出渲染好数据的html页面(简称直出)
    • 直出就是需要node.js的支持,服务器上的浏览器渲染好的东西,直接输出给客户端的浏览器
    • 简单来说,就是直接把配件选好,让店家帮忙组装器,一次性发过来,就是直出这个道理

    重读《JavaScript高级程序设计》

    arguments对象是类数组

    apply()方法接受两个参数:一个是运行函数的作用域,另一个是参数数组,这个参数数组可以是Array实例,也可以是arguments对象(类数组对象)

    function sum(num1 , num2){
    	return num1 + num2;
    }
    function callSum1(num1,num2){
    	return sum.apply(this,arguments); // 传入arguments类数组对象
    }
    function callSum2(num1,num2){
    	return sum.apply(this,[num1 , num2]); // 传入数组
    }
    console.log(callSum1(10 , 10)); // 20
    console.log(callSum2(10 , 10)); // 20
    

    Object.create()和new object()和{}的区别

    Object.create()

    • Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法

    • Object.create()方法接受两个参数:Object.create(obj,propertiesObject) ;

      obj:一个对象,应该是新创建的对象的原型。

      propertiesObject:可选。该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符(这些属性描述符的结构与Object.defineProperties()的第二个参数一样)。注意:该参数对象不能是 undefined,另外只有该对象中自身拥有的可枚举的属性才有效,也就是说该对象的原型链上属性是无效的。

      var o = Object.create(Object.prototype, {
        // foo会成为所创建对象的数据属性
        foo: { 
          writable:true,
          configurable:true,
          value: "hello" 
        },
      

    面试

    跨标签页通讯??

    浏览器下事件循环

    事件循环是指:执行一个宏任务,然后执行清空微任务列表,循环再执行宏任务,再清微任务列表

    从输入 url 到展示的过程

    • DNS解析

    • TCP三次握手

    • 发送请求,分析url,设置请求报文(头,主体)

    • 服务器返回请求的文件(html)

    • 浏览器渲染

      • html parse==>DOM Tree

        标记化算法,进行元素状态的标记

        dom树构建

      • css parser==>Styel tree

        解析css代码,生成样式树

      • attachment==>Render Tree

        结合dom树与style树,生成渲染树

      • layout:布局

      • GPU painting:像素绘制页面

    内存泄露

    • 意外地全局变量,无法被回收
    • 定时器:未被正确关闭,导致所引用的外部变量无法被释放
    • 事件监听:没有正确销毁
    • 闭包:会导致父级中的变量无法被释放
    • dom引用: dom元素被删除时,内存中的引用未被正确清空

    剑指offer??

    描述创建一个对象的过程

    • 新生成了一个对象
    • 链接到原型
    • 绑定this
    • 返回新对象

    JQ的attr和prop的区别

    从源码来看:

    • attr是通过setAttributegetAttribute来设置的,使用的是DOM属性节点
    • prop是通过document.getElementById(el)[name]=vlaue来设置的,是转化为js对象的属性
    • 通过设置checked,selected,readonly,disabled等的时候使用prop效果更好,减少了访问dom属性节点的频率。
    • 一般如果是标签自身自带的属性,我们用prop方法来获取;如果是自定义的属性,我们用attr方法来获取。

    DOM节点的attr和property有何区别

    • property只是一个JS对象的属性的修改
    • Attribute是对html标签属性的修改

    获取当前时间

    new Date().toISOString().slice(0,10)
    

    toLocaleString()

    方法返回一个字符串表示数组中的元素,数组中的元素使用各自toLocaleString方法转成字符串,这些字符串将使用一个特定语言环境的字符串

    var number = 1337;
    var date = new Date();
    var myArr = [number, date, "foo"];
    var str = myArr.toLocaleString(); 
    console.log(str); 
    // 输出 "1,337,2019/2/15 下午8:32:24,foo"
    
    let a=3500
    a.toLocaleString() //3,500
    

    写React/Vue项目时为什么要在组件中写key,其作用是什么

    key的作用是为了在diff算法执行时更快的找到对应的节点,提高diff速度

    undefined.toString()

    和null.toString()一样也是报错的,原始数据类型存储的是值,是没有函数可以调用的

    构造函数的返回值

    • 没有返回值则按照其他语言一样返回实例化对象

    • 若有返回值则检查其返回值是否为引用类型。如果是非引用类型,如基本类型(string,number,boolean,null,undefined)则与无返回值相同,实际返回其实例化对象。

    • 若返回值是引用类型,则实际返回值为这个引用类型。

  • 相关阅读:
    MyBatis 自动关闭 session
    Sublime相关操作及配置
    CentOS yum换源并配置SSH
    SpringSecurity认证流程
    ES模块化的导入和导出
    Promise学习
    axios的使用
    SpringSecurity注解的使用
    Webpack笔记
    JAVA工具类
  • 原文地址:https://www.cnblogs.com/fangdongdemao/p/10400599.html
Copyright © 2020-2023  润新知