Giter Club home page Giter Club logo

front-end-knowledge-note's People

Contributors

ailingangel avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

Forkers

zhangqianfeng

front-end-knowledge-note's Issues

实现ejs的render函数

var str = `
aaaaa
<%=a%>
hhhhh
<% for(var i=0;i<list.length;i++){ %>
  <%=JSON.stringify(list[i])%>
<% }%>
bbbbb
`
var obj = {
  a: 'aaaaaaaaaa',
  list: [
    { item: '1' },
    { item: '2' },
    { item: '3' },
    { item: '4' },
    { item: '5' }
  ]
}
var s = tmpl(str, obj)
console.log(s)


// aaaaa aaaaaaaaaa    {"item":"1"}    {"item":"2"}    {"item":"3"}    {"item":"4"}    {"item":"5"}  bbbbb


function tmpl(str, obj) {
  let func = "let ans; \nwith(data){\n"
  func += "ans = `";
  str = str.replace(/<%=(.*)%>/g, (match, p) => {
    return "${" + p + "}";
  })

  str = str.replace(/<%(.*)%>/g, (match, p) => {
    return "`\n" + p + "\nans+=`"
  })

  func += str + "`\n}\nreturn ans;"
  let fn = new Function("data", func);
  return fn(obj);
}

clientWidth, offsetWidth 和 scrollWidth

image
image

差异

name padding border content vertical-scroll-bar width
scrollWidth ✔️ ✔️
offsetWidth ✔️ ✔️ ✔️ ✔️
clientWidth ✔️ ✔️

如何判断文本是否溢出

上面的三个属性与box-sizing属性的设置无关。即使是在border-box下,clientWidth拿到的还是padding和内容相加得到的值。

单纯使用scrollWidth>clientWidth不能准确得到的溢出的结果,因为scrollWIdth和clientWIdth拿到的都是整数的结果,有的文本实际需要的长度,比如为20.3px,但是通过scrollWidth拿到的为20px,可能与clientWidth20px相等。其实文本是溢出的。

可以通过range来准确获取实际需要的宽度,然后与最终渲染的宽度做对比

const range = document.createRange();
      range.setStart(cellChild, 0);
      range.setEnd(cellChild, cellChild.childNodes.length);
      const rangeWidth = range.getBoundingClientRect().width;
      const padding = (parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
      (parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0);
(rangeWidth + padding > cellChild.clientWidth

迭代器做数组铺平

function findItem(arr, pos) {
    let parrentArray = arr;
    let i = 0;
    while (i < pos.length - 1) {
        parrentArray = parrentArray[pos[i]];
        i++;
    }

    // 当前数组已经访问完了
    if (pos[i] === parrentArray.length) {
        pos.splice(pos.length - 1, 1);
        if (pos.length === 0) {
            return {
                done: true,
                value: undefined
            }
        } else {
            pos[pos.length - 1]++;
            return findItem(arr, pos);
        }
    }
    let item = parrentArray[pos[i]];
    if (Array.isArray(item)) {
        if (item.length > 0) {
            pos.push(0);
            return findItem(arr, pos);
        } else {
            pos[pos.length - 1]++;
            return findItem(arr, pos);
        }
    } else {
        pos[pos.length - 1]++;
        return {
            value: item,
            done: false,
        };
    }
}

function createFlatArrayIterator(arr) {
    let pos = [0];
    return {
        next() {
            return findItem(arr, pos);
        }
    }
}

let iterator = createFlatArrayIterator([
    [],
    ['a'], 'b', 'c', ['d', ['e', 'f', 'g'], 'h']
]);
let flatedNumber = [];
let nextValue = iterator.next();
while (!nextValue.done) {
    flatedNumber.push(nextValue.value);
    nextValue = iterator.next();
}

console.log('ans=>', flatedNumber)

用Iframe集体加载图片

var ifm = document.createElement('iframe')
    document.body.appendChild(ifm)
    var img = document.createElement('img')
    img.src = 'https://web.500px.com/static/media/[email protected]'
    ifm.contentWindow.document.body.appendChild(img)
    setTimeout(function () {
      console.log('stop')
      ifm.contentWindow.stop()
    }, 100)

Promise

1.Promise包含三种状态,pending, fullfilled, rejected

image

2.Promise 的优缺点

优点:

  • 解决回调地域的问题
  • 解决信任问题;在回调函数中把自己的函数交有第三方控制执行,可能会导致没有执行,提前执行,过早执行,执行多次的问题

缺点:

  • 单一值: resolve或者reject只能接受单个的值,如果想要决议多个值,需要将这些值封装在一个数组或者一个对象中
  • 异常捕获的问题: 前一个promise的异常只能被下一个catch或者reject回调捕获。但是如果catch或者reject回调自己也有异常呢?

3.Promise的特点

  • excutor函数接受resolve和reject两个参数
  • excutor函数是同步执行的,then是异步执行的; 即使在excutor函数中没有异步代码,then也是被异步执行
new Promise(function(resolve, reject) {
    console.log('start excutor');
    resolve(100);
    console.log('after resolve');
}).then(function(data) {
    console.log('success', data);
}, function(error) {
    console.log('error', error);
});
console.log('start before then');
// start excutor
// after resolve
// start before then
// success 100
  • 单决议: 一旦决议(resolve或者reject)之后决议结果不能再改变;注意单决议不是说后面的代码不执行了,在excutor函数里只要不return不管是resolve还是reject之后的代码都依然会执行。
new Promise(function(resolve, reject) {
    setTimeout(function() {
        console.log('before resolve');
        resolve(4, 3); // 首次决议,then的resolved回调接收结果
        console.log('after resolve'); // 决议不影响后面的代码执行
        reject(5); // 已经决议过不会被reject的回调接受
        console.log('after reject');//依然会执行
        return 'test'; // return的结果并不会被then的回调函数接收
        console.log('after return'); // 不会执行
    }, 500);
}).then(function(data) {
    console.log(data);// 只会打印4,传递给resolve的第二个参数3被忽略
}, function(error) {
    console.log(error);
});
// before resolve
// after resolve
// after reject
// 4

4.关于then

  • 每次调用then或者catch都会返回一个新的promise
let p1 = new Promise(function(resolve, reject) {
    resolve(100);
});
let p2 = p1.then(function(data) {
    return data;
});
console.log(p1 === p2)// false
  • then中的返回结果会作为下一次then的结果。在then中可以使用return含义不同于在excutor函数中使用return;
  • 在then中使用return 返回一个基本类型的值会被作为下一次的成功态
new Promise(function(resolve, reject) {
    resolve(100);
}).then(function(data) {
    return data;
}).then(function(data) {
    console.log('success', data); // 走到这里, success 100
}, function(error) {
    console.log('error', error);
});
  • 在then中不显示调用return则就当做return undefined来处理
new Promise(function(resolve, reject) {
    resolve(100);
}).then(function(data) {
    // return data;
}).then(function(data) {
    console.log('success', data); // 走到这里, success undefined
}, function(error) {
    console.log('error', error);
});
  • 在then中(完成回调或拒绝回调)可以return一个promise,这个promise会被展开。就是下一个then中收到的结果是这个return promise的结果,这个promise是成功的就走成功回调,是失败的就走失败回调。
new Promise(function(resolve, reject) {
    resolve(100);
}).then(function(data) {
    return new Promise(function(resolve, reject) {
        resolve(data * 2);
    });
}).then(function(data) {
    console.log('success', data);// 走到这里 success 200
}, function(error) {
    console.log('error', error);
});

new Promise(function(resolve, reject) {
    resolve(100);
}).then(function(data) {
    return new Promise(function(resolve, reject) {
        reject(data * 2);
    });
}).then(function(data) {
    console.log('success', data);
}, function(error) {
    console.log('error', error); // 走到这里 error 200
});

new Promise(function(resolve, reject) {
    reject(100);
}).then(function(data) {
    throw new Promise(function(resolve, reject) {
        resolve(data * 2);
    });
}, function(error) {
    console.log('error', error) // 走到这里来
    return error * 2; // return 返回这个基本值表示走下一个then的成功回调
}).then(function(data) {
    console.log('success', data); // 走到这里 success 200
}, function(error) {
    console.log('error', error);
});
  • 在then中(完成回调或拒绝回调)throw一个Error, 或者throw一个 promise,会走到下一个then中的rejected回调; 如果throw一个Promise这个Promise不会被展开,而是原模原样传递给rejected回调函数
new Promise(function(resolve, reject) {
    resolve(100);
}).then(function(data) {
    throw 2
}).then(function(data) {
    console.log('success', data);
}, function(error) {
    console.log('error', error); // 走到这里 error 2
});

new Promise(function(resolve, reject) {
    resolve(100);
}).then(function(data) {
    throw new Promise(function(resolve, reject) {
        resolve(data * 2);
    });
}).then(function(data) {
    console.log('success', data);
}, function(error) {
    console.log('error', error); // 走到这里 error Promise {200}; 这里error就是被上层throw的Promise对象
});

5.Promise的拒绝捕获

  • Promise将程序执行的异常也处理成异步的,走reject逻辑
  • rejected回调也会返回一个promise
new Promise(function(resolve, reject) {
    throw TypeError('type is not support')
}).then(function(data) {
    console.log(data);
}, function(error) {
    console.log('error:', error)// 走到这里 error, TypeError: type is not support
}).then(function(data) {
    console.log('here', data) // 走到这里 here undefined, 因为上一个rejected回调没有拒绝,默认走resolved回调; 没有显示return一个值,采用默认的undefined
})
  • 可以用catch而不是reject回调来捕获这个拒绝
new Promise(function(resolve, reject) {
    reject('type is not support')
}).catch(function(data) {
    console.log('catch', data) // 走到这里 catch type is not support
})
  • catch和reject回调可以共存,捕获才去就近原则
new Promise(function(resolve, reject) {
    reject('type is not support')
}).then(function(data) {
    console.log('success', data);
}, function(error) {
    console.log('error', error); // 走到这里不走catch; error type is not support
}).catch(function(data) {
    console.log('catch', data);
})

new Promise(function(resolve, reject) {
    reject('type is not support')
}).catch(function(data) {
    console.log('catch', data); // 走到这里 catch type is not support
}).then(function(data) {
    console.log('success', data); // 走到这了 success undefined 前一个catch默认使用return undefined
}, function(error) {
    console.log('error', error);
})

6.Promise.resolve(value)将value封装并 返回一个Promise对象

Promise.resolve(value)是下列代码的简写

new Promise(function(resolve){
    resolve(value)
});
  • 如果value不是promise也不是thenable, 返回以这个value值决议的promise对象(resolved回调)
Promise.resolve({ a: 1 }).then(function(data) {
    console.log(data)
});
// {a:1}
  • 如果value是一个thenable,决议的结果取决于这个对象的then方法
var thenable = {
    then: function(resolve) {
        resolve("Resolving");
        throw new TypeError("Throwing");
    }
};

var p3 = Promise.resolve(thenable);
p3.then(function(v) {
    console.log(v); // 输出"Resolving"
}, function(e) {
    // 不会被调用
});
  • value是一个promise, 会对这个promise进行展开;最终的决议结果取决于最里层promise的决议结果
var p1 = Promise.resolve('p1');
var p2 = Promise.resolve(p1);
var p = Promise.resolve(p2);
console.log(p === p1); // true
p.then(function(data) {
    console.log(data); // 'p1'
})

var p1 = Promise.reject('p1');
var p2 = Promise.resolve(p1);
var p = Promise.resolve(p2);
p.then(function(data) {
    console.log(data);
}, function(error) {
    console.log('error', error);// 走到这里 error p1
})

7.Promise.reject(reason)返回一个带有拒绝理由reason的promise

  • Promise.reject是以下代码的简写
new Promise(function(resolve, reject) {
    reject(reason);
});
  • 如果reason是一个Promise, Promise.reject不对reason进行展开,而是以把这个promise的值作为reason传递下去
var p1 = Promise.reject('p1');
var p2 = Promise.reject(p1);
var p = Promise.resolve(p2);
console.log(p === p1); // false
console.log(p === p2); // true
console.log(p1 === p2);//false
p.then(function(data) {
    console.log(data);
}, function(error) {
    console.log('error', error); // 走到这里 error 是Promise p1 不是字符串"p1"
})

8. Promise中的静态方法 Promise.race 和Promise.all

  • Promise.all 接受一个iterable对象,并返回一个promise对象
  • Iterable 中所有的promise都完成了,这个Promise才算完成;返回结果为数组依次对应每个promise完成的结果
  • Iterable中只要有一个失败了,就算失败,失败原因是第一个失败的promise的原因
  • 如果iterable是空数组,立刻决议返回[]

下面列出了Promise.all的源代码实现:

Promise.all = function(iterable) {
    return new Promise(function(resolve, reject) {
        if (typeof iterable !== 'object' && typeof iterable[Symbol.iterator] !== 'function') {
            reject('type error');
        } else if (iterable.length === 0) {
            resolve([]);
        } else {
            let ans = [];
            let len = 0;
            for (let i = 0; i < iterable.length; i++) { // 注意使用let来声明块作用域,闭包!!!
                Promise.resolve(iterable[i]).then(function(data) {
                    len++; // 使用len来追踪获取到了多少个结果; 不能使用ans.length来判断 因为ans[i]的方式会创建稀疏数组导致ans.length不能用来决议的promise的个数
                    ans[i] = data; // 必须使用下标复制 因为不知道谁先到 如果使用ans.push(data)不能保证结果的顺序
                    if (len === iterable.length) {
                        resolve(ans);
                    }
                }).catch(function(error) {
                    reject(error);
                });
            }
        }
    });
}

var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 300, 'foo');
});

var p4 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'bar');
});
Promise.all([p1, p2, p3, p4]).then(values => {
    console.log(values); // [3, 1337, "foo", "bar"] 
});
  • Promise.race 接受一个iterable对象,一旦其中的一个promise 决议就立刻决议
  • 如果传入空数组永远不决议!区别于Promise.all

下面是Promise.race的实现

Promise.race = function(iterable) {
    return new Promise(function(resolve, reject) {
        if (typeof iterable !== 'object' && typeof iterable[Symbol.iterator] !== 'function') {
            reject('type error');
        } else if (iterable.length !== 0) {
            for (let i = 0; i < iterable.length; i++) {
                Promise.resolve(iterable[i]).then(function(data) {
                    resolve(data);
                }).catch(function(error) {
                    reject(error);
                });
            }
        }
    });
}

9.Promise.finally的实现

Promise.prototype.finally = function(cb) {
    return this.then(function(data) {
        return Promise.resolve(cb()).then(function() {
            return data;
        });
    }, function(error) {
        return Promise.resolve(cb()).then(function() {
            throw error;
        });
    });
};

let, var 和const的区别

1.变量提升

var会进行变量提升,let和const不会进行提升

2.暂存死区

因为var会进行变量提升,所以可以在声明之前访问,不会形成暂存死区。let 和const 不会进行变量提升,在声明之前不能使用,形成暂存死区

3.重复声明

var可以进行重复声明,但是let和const不能进行重复声明

4.块作用域

var不会形成块作用域,let和const可以形成块作用域

5.重新赋值

var和let声明的变量可以重新赋值,const不可以。如果const 声明的变量存储的是引用地址, 是可以修改这个引用对应的对象的值的,但是这个变量不能被赋予其他值

ES6模块化

情况1: 假设有一个文件a.js里面的代码如下

const random = Math.random();
export const loginUrl = `http://test.com?number=${random}`;

在b.js中使用loginUrl

import {loginUrl} from './a.js';

function test(){
    const test = Math.random();
    console.log(loginUrl);
}

test();
test();

两次test调用的结果是?

情况2: 如果将a.js文件中改成

const random = Math.random();
export const loginUrl = ()=>`http://test.com?number=${random}`;

然后将b.js文件的loginUrl改成函数调用

import {loginUrl} from './a.js';

function test(){
    const test = Math.random();
    console.log(loginUrl());
}

test();
test();

两次运行的结果?

情况3: 在情况2的基础上修改a.js代码

export const loginUrl = ()=>{
    const random = Math.random();
    return `http://test.com?number=${random}`;
}

类型转换

1.显示强制类型转换

  • 使用+操作符将一个值转变成数字
+'10' // 10
  • 和''进行+运算可以值转变成字符串
'' + 1 -> '1'
  • 使用!可以将值转变成boolean类型并同时取反,因此!!可以显示把一个值转变成boolean类型
!![] // true
  • 注意区分Number()和new Number()
// new Number是将一个值封装成数字对象类型
var test = new Number(1)
typeof test === 'object'
// Number()是将一个值强制类型转换成数字
typeof Number('1')  === 'number'

2. + 操作符

a + b

  • 如果a和b其中有一个是字符串就执行字符串拼接操作
'1' + 1 // '11' 
  • 如果a和b都是数字就执行加法运算
  • 如果有一个是boolean类型,就将boolean类型转换成数字类型执行加法运算
true + 1 // 2
!![] + 1 // 2 
  • 如果有一个是引用类型,就对引用类型执行toPrimitive操作(先调用valueOf()方法,如果返回的不是基本类型就继续调用toString()方法), 再走上面的步骤
{} + 1 // '[object Object]1'
var obj = {
  valueOf(){
    return 1
  }
}
console.log(obj + 1) // 2

3. - / * 操作符

因为这几个操作符都是针对数字的运算,因此会将变量强制类型转换成数字

true // 1
false // 1
'' //  0
null // 0
isNaN(undefined) // true
// 引用类型就采用toPrimitive操作, 引用类型的valueOf方法默认返回自己,除非自己显示定义, 不能返回基本类型就继续调用toString方法, 数组的toString默认返回逗号分隔的字符串, 对象默认返回'[object Object]'
// 数组
[] -> '' -> 0
{} -> [object Object] -> NaN

4. falsy值

  • 0
  • undefined
  • null
  • NaN
  • ''
    注意[] 和{} 是trusy的值

5. == 和===操作符

== 和===最大的区别在于==允许强制类型转换,而===不允许进行强制类型转换

转换规则

  • 如果a 和 b 为null或者undefined 返回true
  • 如果a和b都是对象,那么==和===的行为一样,比较的是对象的引用地址
  • 如果a或b是字符串,就将字符串转变成数字
  • 如果a或者b是boolean类型,就将boolean值转变成数字
  • 如果a或者b是引用类型,就对引用类型的值进行toPrimitive操作,对于封装的数据类型会自动进行拆封
console.log(2.0 == “2” == new Boolean(true) == “1”) // true
// 2.0 == '2' 返回true
// true == new Boolean(true) true变成数字类型1,然后对new Boolean(true)进行拆封, 返回 true
// true == '1'  首先true转变成数字类型1,然后字符串'1'转变成数字类型1 所以最终返回true

![] == [] //true
// ![]是布尔值返回false
// fasle == [] 将布尔值转变成数字类型0
// 0 == [] 将[]执行toPrimitive操作返回字符串'' 
// 0 == '' 字符串''转变成数字类型 0 所以最终结果返回true

!{} == {} // false
// !{}转变成boolean类型 false
// false == {} false转变成数字类型0
// 0 == {} 对{}执行toPrimitive操作返回'[object Object]'
// 0 == '[object Object]' 字符串转变成数字类型NaN
// NaN和任何值都不相等包括他自己

注意事项

  • NaN和任何值都不相等包括他自己(window.isNaN)
  • ===操作符无法区分+0和-0
  • Object.is(NaN, NaN)返回true
  • Object.is(+0, -0)返回false

6.抽象关系比较

抽象关系比较会进行字符串比较,如果出现非字符串的值就转换成数字再进行比较

{a:1} < {b:1} // false
[0, 1, 2] < [1, 0] // true
[0] < 2 // true

变量类型检测

JS中的基本数据类型氛围基本类型和引用类型

基本类型: null undefined number string boolean symbol(es6新增)
引用类型: object

检测数据的基本类型有如下几种方法
1. typeof 操作符

缺点: 不能区分内置对象的类型(function 除外)以及自定义的类型

// 不能区分 null 以及内置对象类型, function 除外
typeof null === 'object'
typeof function(){} === 'function'
typeof [] === 'object'
typeof new Boolean(true) === 'object'
// 不能区分自定义数据类型
function myValue() {}
var test = new myValue();
typeof test === 'object';

2. Object.prototype.toString 方法

可以区分内置对象的类型,但是不能区分自定义对象的类型; 返回类属性

Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(1)// '[object Number]'
// 可以区分内置对象的类型
Object.prototype.toString.call([])// '[object Array]'
// 不能区分自定义类型
function myVal() {}
var test = new myVal();
Object.prototype.toString.call(test) // '[object Object]'

3..instanceof 操作符

可以区分自定义类型,内置对象类型,但是无法检测基本数据类型;因为instanceof操作检测是一个对象的原型是否出现在另一个对象的原型链上,因此要求这两个对象必须属于同一个全局环境,也就是说无法检测一个窗口内的对象是否是另一个窗口(iframe)中类型的对象

// 无法检测基本数据类型
null instanceof Object // false
1 instanceof Number // false
// 可以检测内置对象的类型
[] instanceof Array // true
// 可以检测自定义数据类型
function myVal() {}
var test = new myVal();
test instanceof myVal // true

// 必须要在同一个环境中
a instanceof A // 构造函数A必须和a处于同一个执行环境中

function A(){}
function test(){
    function A(){}
    var a = new window.A();
    console.log(a instanceof A); // false
    console.log(a instanceof window.A); // true
}
test();

4.利用constructor

只能用来判断引用类型,无法判断基础数据类型; 而且对象的原型链可能被整个替换,拿到的constructor 不一定准确

[].constructor === Array // true
function myVal() {}
var test = new myVal(); 
myVal.prototype = {}
test.constructor === myVal // true
var test2 = new MyVal()
test2.constructor === myVal // false, test2.constructor 是Object, 继承自新的原型对象的constructor属性
myVal.prototype.constructor = myVal; // 修正constructor的指向
var test3 = new myVal()
test3.constructor === myVal;

this和对象

this

JS中函数的this是动态绑定的,并不采用词法作用域规则。this和arguments都不会沿着作用域向上查找

this的绑定规则

  • 默认绑定(非严格模式下绑定到window对象,否则绑定为undefined)
function test(){
  alert(this); // [object Window]
}
test();
  • 隐式绑定(当函数作为某个对象的方法调用时,this绑定到这个对象上; 如果通过多个对象的引用调用这个函数,那么以最后一个对象为准)
function test(){
  alert(this.a); // test
}
var obj = {
  a:'test',
  fn: test
}
obj.fn();
function test(){
  alert(this.a); // test
}
var obj1 = {
  a:'test',
  fn: test
}

var obj2 = {
  a: 'obj2',
  obj1: obj1
}
obj2.obj1.fn() //以obj1为准

绑定丢失: 在将函数作为参数传递给另一个函数或者将函数赋值给另一个变量的时候就容易导致绑定丢失的问题

function test(){
  alert(this); //  [object Window]
}
var obj1 = {
  a:'test',
  fn: test
}

var foo = obj1.fn;
foo();
  • 显示绑定(显示指定函数的this值)
    call 和apply都可以用来显示指定函数的this所绑定的对象,区别在于第二个参数.call的其余参数和函数本身所接受的参数保持一致,而apply的第二个参数是一个数组或者类数组作为原函数调用要用的参数;bind会返回一个指定了上下文对象的函数;

call的实现

Function.prototype.call = function(context) {
    if (!context) {
        context = typeof window === 'undefined' ? global : window;
    }
    context.fn = this;
    let args = [...arguments].slice(1);
    let res = context.fn(...args);
    delete context.fn;
    return res;
}

apply的实现

Function.prototype.apply = function(context, args = []) {
    if (!context) {
        context = typeof window === 'undefined' ? global : window;
    }
    context.fn = this;
    let res = context.fn(...args);
    delete context.fn;
    return res;
}
```js

bind实现
```js
Function.prototype.bind = function(context) {
    if (!context) {
        context = typeof window === 'undefined' ? global : window;
    }
    let oldArgs = Array.prototype.slice(arguments, 1);
    let self = this;
    return function() {
        let args = oldArgs.concat([...arguments]);
        let res = self.apply(context, args);
        return res;
    }
}
  • new 绑定
function newFn(fn) {
    return function() {
        // let obj = {};
        // obj.__proto__ = fn.prototype;
        let obj = Object.create(fn.prototype);
        let res = fn.apply(obj, arguments);
        //注意判断条件
        if ((typeof res === 'object' && res !== null) || typeof res === 'function') {
            return res;
        }
        return obj;
    }
}

绑定优先级

  • new 绑定
  • 显示绑定
  • 隐式绑定
  • 默认绑定

对象

创建对象的方式

  1. 字面量(推荐: 可以一次设置多个属性,减少代码量,直观)
  2. new Object()

对象的属性

对象的属性分为数据属性和访问器属性

  1. 数据属性具有的特性有
    configurable: 是否可以配置,典型的能否应用delete操作符删除;改成false的操作是不可逆的
    enumerable: 是否可以枚举,能否通过for...in循环便利到
    writable: 是否可写,是否可以修改属性对应的值
    value: 属性对应的值

  2. 访问器属性具有的特性有
    set: 属性的set函数,只要具有set函数就说明数据可写
    get: 属性是否可被访问
    configurable:属性可以配置吗
    enumerable: 属性可以枚举吗

设置对象的属性

  • 如果对象自身具有这个属性,那么就改变这个属性的值
  • 如果对象的原型链上不包含这个属性,就给这个对象创建一个新的属性
  • 如果属性是继承来的,如果是只读的赋值操作就失败;如果是可写的就创建一个同名的属性覆盖这个继承来的属性;如果继承来的属性有set函数就调用这个set函数

备注: 通过字面量创建的对象,默认是可写的课枚举的可配置的。如果是通过Object.defineProperty那么属性默认是不可配置不可枚举的;

对象的不可变性

  • 设置数据对象的writable和configurable是false
  • 调用Object.preventExtension(obj)让对象不可扩展,但是不会影响对象现有的属性
  • 调用Object.seal(obj), 在Object.preventExtension的基础上让对象的属性变成不可配置的
  • 调用Object.freez(obj), 在Object.preventExtension的基础上让对象的属性变成不可配置的,并且不可写

对象的枚举

  • for...of 实现了iterator接口的对象可以使用for...of来遍历
  • for...in 遍历对象自身以及原型链上可枚举的属性
  • Object.keys(obj)返回对象自身可枚举属性的字符串数组

属性的存在性

  • key in obj, 判断一个属性是否存在对象上,能区分存在但值为undefined以及不存在的属性
  • hasOwnProperty, 判断一个属性是否是自身的而不是继承而来的

对象的深拷贝

function deepClone(obj) {
    if (typeof obj !== 'object') return obj;
    if (obj === null) return obj;
    let copy = new obj.constructor();
    for (let key in obj) {
        if (typeof obj[key] === 'object') {
            copy[key] = deepClone(obj[key]);
        } else {
            copy[key] = obj[key];
        }
    }
    return copy;
}

将数组扁平化去并除其中重复部分数据,得到一个升序且不重复的数组

将数组扁平化去并除其中重复部分数据,得到一个升序且不重复的数组
例 [
[1, 2, 2],
[3, 4, 5, 5],
[6, 7, 8, 9, [11, 12, [12, 13, [14]]]],
10
] => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

function flatAndSortArray(arr) {
    const flatedArr = flatArray(arr);
    flatedArr.sort((a,b)=>a-b);
    let i = 0;
    let j = 0;
    while(j < flatedArr.length) {
        if(flatedArr[i] !== flatedArr[j]){
            flatedArr[++i] = flatedArr[j];
        }
        j++;
    }
    flatedArr.splice(i+1);
    return flatedArr;
}

function flatArray(arr) {
    return arr.reduce((acc, item)=> {
        if(Array.isArray(item)){
            return acc.concat(flatArray(item))
        }
        return acc.concat(item);
    },[]);
}

将驼峰转换成中线命名

function styleHyphenFormat(propertyName) {
    function upperToHyphenLower(match) {
      return '-' + match.toLowerCase();
    }
    return propertyName.replace(/[A-Z]/g, upperToHyphenLower);
  }

  console.log(styleHyphenFormat('borderTop'))

函数

函数科里化

只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数

function curry(fn) {
    let oldArgs = Array.prototype.slice.call(arguments, 1);
    return function() {
        let newArgs = Array.prototype.slice.call(arguments);
        let args = newArgs.concat(oldArgs);
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        } else {
            return curry.call(this, fn, ...args);
        }
    }
}

function sum(a, b, c) {
    return a + b + c;
}

console.log(curry(sum)(1, 2, 3))
console.log(curry(sum, 1)(2, 3))
console.log(curry(sum, 1, 2)(3))

习题

// 第一题:
var foo = 'hello';
(function() {
    var foo = foo || 'word'; // foo会进行提升覆盖了全局作用域中的foo
    console.log(foo); // word
})();

// 第二题:
console.log(parseInt(' 01')) //1
console.log(parseInt('0918abc')) // 918

// 第三题:
var ninjia = function myNinJia() {
    console.log(ninjia === myNinJia); // 在函数表达式中如果给函数了一个名字,那么这个名字只能在函数内部作用域中访问
}

ninjia() // true;
myNinJia() // reference error]

// 第四题:
var f = function() {
    var a = b = 1; // b没有进行生命,泄漏到了全局作用域中
}

f();
console.log(b); // 1
console.log(a); // a is not defined , reference error

// 第五题:
var f = function() {
    var a = b = 1;
}

setTimeout(f, 0);
console.log(b); // reference error b is not defined

// 第六题:
var a, b = 0;
fn = function() {
    var a = b = 1;
}
fn();
console.log(a) // undefined;
console.log(b) // 1

// 第七题: 循环执行完之后tc最后的值为最后一个定时器,因此setTimeout的回调中取消的是最后一个定时器,前面的定时器不受影响
function f() {
    for (var i = 0; i < 4; i++) {
        var tc = setTimeout(function(i) {
            console.log(i);
            clearTimeout(tc);
        }, 10, i);
    }
}
f(); // 0, 1, 2

// 第八题: tc作为参数传给setInterval回调,tc是上一次的定时器, 最后一个定时器无法被取消
function fn() {
    for (var i = 0; i < 4; i++) {
        var tc = setInterval(function(i, tc) {
            console.log(i);
            clearInterval(tc);
        }, 10, i, tc);
    }
}
fn(); // 0, 1, 2, 3,3,3,3,3

// 第九题:
function foo(a) {
    var a; //重复声明被忽略
    return a; // hello
}
console.log(foo('hello'));

// 第十题:
function foo(a) {
    var a = 'bye'; //赋值被覆盖
    return a; // bye
}
console.log(foo('hello'));

// 第十一题: 立即函数表达式中if中的name因为用var进行了声明,最终被提升了
var name = 'word';
(function() {
    if (typeof name === 'undefined') {
        var name = 'Jack'; //提升
        console.log('Goodbye' + name);
    } else {
        console.log('hello' + name);
    }
})(); // 'Goodbye Jack';

// 第十二题:
var number = 5;
var obj = {
    number: 3,
    fn1: (function() {
        var number;
        this.number *= 2;
        number = number * 2;
        number = 3;
        return function() {
            var num = this.number;
            this.number *= 2;
            console.log(num);
            number *= 3;
            console.log(number);
        }
    })()
}
var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(number);
// 10 9 3 27 20
// 第十三题: 数字的每一个值是一个基本类型的变量传递给forEach的回调函数中,使用的是值传递,不会影响数组本身的元素
let arry = [1, 2, 3, 4];

arry.forEach(item => {
    item *= 10;
});
console.log(arry); //[1, 2, 3, 4]

// 第十四题:
function countFunc() {
    let funcs = [];
    for (var i = 0; i < 10; i++) {
        funcs[i] = function() {
            return i;
        }
    }
    return funcs;
}

var funcs = countFunc();
console.log(funcs[5]()) // 10 多个闭包共享作用域

高阶函数的应用

高阶函数: 函数作为值进行传递或者作为值被返回

通用的检查变量类型的函数

function checkType(type) {
  return function(x) {
    return Object.prototype.toString.call(x) === `[object ${type}]`
  }
}
var isString = checkType('String');
alert(isString(1))

ES6 vs CommonJs

差异

动态vs静态

commonJs:

  • 动态:模块的依赖建立在代码运行阶段
  • require里面可以是表达式
  • 可以使用if语句判断是否加载某个模块

es6:

  • 静态 :模块的依赖建立在代码编译阶段
  • 导入和导出都是声明式的,不支持导入的路径是一个表达式
  • 导入和导出语句必须位于顶层作用于,不可以放到if条件语句中

因此,相比于commonJs, es6有一下几点优势

  • 死代码检测和排除
    用静态工具检测哪些模块没有被调用过,通过静态分析可以再打包的时候去掉这些未曾使用的模块,以减小打包的体积
  • 模块变量类型检查
    js是动态类型的语言,不会再代码执行前检查类型错误,es6的静态模块结构有助于确保模块之间传递的值或者接口类型是正确的
  • 编译器优化
    commonJs本质上导入的都是一个对象,但es6 module支持直接导入变量,减少了引用层级,程序效率更高

值拷贝和动态映射

commonJs: 导出的是一份值的拷贝
es6 Module: 导出的是值的动态映射,并且这个映射是只读的

// calculator.js
var count = 0;
module.exports = {
    count: count,
    add: function(a, b) {
        count++;
        return a + b;
    }
}
// index.js
var count = require('./calculator.js').count;
var add = require('./calculator.js').add;
console.log('initial count=>', count);
add(2, 3);
console.log('after add=>',count);
count+=2;
console.log('add count=>', count);

运行结果如下:
image

let count = 0;
const add = function(a, b) {
    count++;
    return a + b;
}
export {count, add};
import {count, add} from './calculator';
// eslint-disable-next-line no-console
console.log('initial count=>', count);
add(2, 3);
console.log('after add=>', count);
count +=1;

image

相同点

  • 都是同步加载的,并且只在加载的时候执行一次,如果执行过了再次引入不会再执行

实现EventEmitter类,需要实现on,off,once,trigger几个方法

class EventEmitter {
    constructor(){
        // 存每一个event对应的cb, 结构如下
        // connection -> {function: function(){}, once: true}
        this.eventCbMap = new Map();
    }

    on(event, cb){
        this.pushCbIntoQueue(event, cb, false);
    }
    off(event, cb){
        const queue = this.eventCbMap.get(event);
        if(queue) {
            const index = queue.findIndex(item=>item.cb === cb)
            queue.splice(index, 1);
        }
    }
    once(event, cb){
        this.pushCbIntoQueue(event, cb, true);
    }
    trigger(event, ...args){
        const queue = this.eventCbMap.get(event);
        if(queue && queue.length > 0) {
            let i = 0;
            while(i < queue.length) {
                const {cb, once} = queue[i];
                cb(...args);
                if(once) {
                    // 将once的侦听移除;
                    queue.splice(i, 1);
                } else {
                    i++;
                }
            }
        }
    }
    pushCbIntoQueue(event, cb, isOnce=false) {
        const queue = this.eventCbMap.get(event);
        if(queue) {
            queue.push({cb, once: isOnce});
        } else {
            this.eventCbMap.set(event, [{cb, once: isOnce}]);
        }
    }
}

const event = new EventEmitter();

let cb1 = function(...args) {
    console.log('cb1', args);
};

let cb2 = function(...args) {
    console.log('cb2', args);
};

let cb3 = function(...args) {
    console.log('cb3', args);
};

let cb4 = function(...args) {
    console.log('cb4', args);
};

event.on('event-one', cb1);
event.on('event-one', cb2);
event.once('event-one', cb3);
event.on('event-two', cb4)


console.log('-------')
event.trigger('event-one', 'a', 'b');
event.trigger('event-two', 'a', 'b');
console.log('-------')
event.trigger('event-one', 'a', 'b');
event.trigger('event-two', 'a', 'b');
event.off('event-one', cb1);
console.log('-------')
event.trigger('event-one', 'a', 'b');
event.trigger('event-two', 'a', 'b');

原型,原型链和继承

概念

原型

  • JS是真正的面向对象的语言,并不存在类的概念。每一个JS对象都有一个[[prototype]]指向它的原型,通过这个原型对象可以继承某些属性和方法

原型链

  • JS对象的原型也是一个对象,这个原型对象也有自己的原型,这样依次链接下去直到原型对象为null为止。
  • 访问JS对象的属性或者方法的时候就会开始从对象本身找,如果找不到就沿着原型链找直到到达顶层,如果依然没找到就会返回undefined

继承

  • JS是通过原型链实现继承的,将对象的[[prorototype]]指向一个对象,就能从这个对象上继承属性或者方法。
  • JS对象具有自有属性,同时也从原型对象上来继承一些属性。JS通过原型链来实现继承

只有在查找某个对象的属性时才会体现出JS的继承特性, 查找规则如下先从对象自身开始查找,如果有就返回这个属性的值,如果没有就查找原型对象,直到原型对象为null为止;对象自身的同名属性可以屏蔽原型链上的同名属性

构造器

函数可以通过new 操作符来调用,该调用叫做函数的构造调用,函数就叫做构造器(却别与其他语言的类, JS中不存在类);当函数通过new 来调用时会发生以下的操作:

  • 新创建一个对象
  • 将这个新创建的对象的原型链接到构造器的prototype上
  • 在这个新对象的上下文中调用这个函数
  • 如果函数返回了对象或者函数那么就直接返回这个对象或者函数,否则返回这个新创建的对象
    注意
    每个函数都有一个prototype属性,当函数通过new 来调用时新创建的对象的[[prototype]]会自动指向这个函数的prototype属性;这个原型对象自带一个constructor属性指向这个构造器
// new操作符的原生js实现
function _new(fn) {
    return function() {
        let target = Object.create(fn.prototype);
        let res = fn.apply(target, arguments);
        if ((typeof res === 'object' && res) || typeof res === 'function') {
            return res;
        }
        return target;
    }
}

对象 构造器以及原型之间的关系

image

用原型链来实现继承

function SuperType(name) {
    this.name = name;
}

SuperType.prototype.getName = function() {
    return this.name;
};

function SubType(name, age) {
    SuperType.call(this, name);
    this.age = age;
}
// 使用Object.create来创建一个以SuperType.prototype为原型对象的对象
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.getAge = function() {
    return this.age;
}
SubType.prototype.constructor = SubType; // 修正constructor的指向

let test = new SubType('a1', 12);
console.log(test.getAge()); // 12
console.log(test.getName()); // a1

注意上述代码中SubType继承SuperType的时候使用的是Object.create(SuperType.prototype)新创建了一个以SuperType.prototype为原型对象的对象,并没有使用new SuperType()的方式,是因为SuperType构造器可能导致某些副作用或者里面新创建的属性并不期望被放在原型对象上被其他对象继承,而是希望维护单独的一份数据,所以在SubType的构造器中通过调用SuperType.call(this, name)的方式保证SubType的实例对象拥有一份独立的数据而不是共享的数据

面向对象中的几个关键函数

instanceof 操作符

instanceof操作符用来判断一个函数的原型对象是否出现在另一个对象的原型链中,这个操作符比较的是对象和构造器之间的关系

function intanceof(a, A) {
    let proto = A.prototype;
    let _proto_ = a.__proto__;
    while (_proto_) {
        if (_proto_ === proto) {
            return true;
        }
        _proto_ = _proto_.__proto__;
    }
    return false;
}

isPrototypeOf

isPrototypeOf 用来判断一个对象是否出现在另一个对象的原型链上

function isPrototypeOf(a, b) {
    var _proto = b.__proto__;
    while (_proto) {
        if (_proto === a) {
            return true;
        }
        _proto = _proto.__proto__;
    }
    return false;
}

let a = {};
let b = Object.create(a);
console.log(isPrototypeOf(a, b)) // true

getPrototypeof

获取某个对象的原型对象

let a = {};
let b = Object.create(a);
console.log(isPrototypeOf(a, b)) // true
function getPrototypeOf(obj) {
    if ('__proto__' in obj) {
        return obj.__proto__;
    }
    return obj.constructor.prototype;
}

console.log(getPrototypeOf(b)); // {}

Object.create

创建一个以指定对象为原型对象的对象

function objectCreate(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}

let c = { a: 1 };
let d = objectCreate(c);
console.log(d); // {}
console.log(c.isPrototypeOf(d)) //true

如何不使用new来实例化一个类

function MyClass(a, b) {
		if(!(this instanceof MyClass)) {
    		let obj = Object.create(MyClass.prototype);
        return MyClass.apply(obj, arguments);
    }
    this.a = a;
    this.b = b;
    return this;
}

let foo = new MyClass('a0', 'b0');
let bar = MyClass('a1', 'b1');
console.log(foo, bar);

console.log(foo instanceof MyClass, bar instanceof MyClass);

如何动态载入JS文件

1.给script标签添加defer属性

  • defer属性会让js并行下载,但是要等到HTML解析完成之后,在window.onload事件之前执行
  • 添加了defer属性的js文件执行的顺序和在文档中定义的顺序一样
<script src="../your_file.js" defer></script>

2.给script标签添加async属性

  • async属性会让js并行下载,但是js文件下载完成之后立刻执行无论html是否解析完毕
  • 添加了async属性的js文件执行顺序不能保证
<script src="../your_file.js" async></script>

3.动态创建script标签

  • 和img标签不一样,设置了script的src并不会开始下载,而是要添加到文档中Js文件才会开始下载
let script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'your_file.js';
// 只有添加到html文件中才会开始下载
document.body.append(script);

4.使用xhr脚本注入

  • 会受到同源策略的限制
 var xhr = new XMLHttpRequest();
 xhr.open("get", "your_file.js", true);
 xhr.onreadystatechange = function() {
     if (xhr.readyState == 4) {
         if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
             let script = document.createElement('script');
             script.type = 'text/javascript';
             script.src = 'your_file.js';
             script.text = xhr.responseText;
             // 只有添加到html文件中才会开始下载
             document.body.append(script);
         }
     }
 }
 xhr.send(null);

函数节流&函数防抖

函数节流

如果一个事件连续触发,那么只在delay间隔内回调函数顶多调用一次, 函数节流可以减少连续事件触发时回调函数的执行次数
应用场景:

  • 在无线滚动当中,要不停的检测用户离这个页面的底部有多远,当达到一定距离就开始发请求获取更多的信息。检测距离底部的距离时,这个检测的函数应该使用函数节流,在一定的时间间隔内去检查。这里不能使用函数抖动,函数抖动只会在用户停止滚动鼠标之后才会触发。
function throttle(fn, delay) {
    let pre = 0;
    return function() {
        let now = Date.now();
        if (now - pre >= delay) {
            fn.apply(this, arguments);
            pre = now;
        }
    }
}

function throttle(fn, delay) {
    let timer = null;
    return function() {
        let args = arguments;
        let context = this;
        if (!timer) {
            fn.apply(context, args);
  timer = setTimeout(function() {
            timer = null;
        }, delay);
        }
      
    }
}

函数防抖

和函数节流一样,函数防抖也是用来控制函数执行的次数的;不一样的是在连续事件触发的过程中,回调函数要么立即执行在之后不执行,只有在事件停止之后的delay时间后才可以再次执行。 要么在事件停止之后的delay时间之后再执行,开始不执行。

  • 延迟执行版

应用场景:

  • window的onresize属性
  • 在输入框中输入内容并发送ajax请求,没有必要用户输入就发送请求,可以等到用户输入完了再发送请求
function debounce(fn, delay) {
    let timer = null;
    return function() {
      clearTimeout(timer);
      let args = arguments;
      timer = setTimeout(()=>{
        fn.apply(this, args);
      }, delay);
    }
}
var count = 1;
document.body.onresize = debounce(function(){
  console.log(count++);
}, 5000);
  • 立即执行版
function debounce(fn, delay) {
    let timer = null;
    return function() {
      clearTimeout(timer);
      if(!timer) {
        fn.apply(this, arguments);
      }
      timer = setTimeout(function(){
         timer = null;
      }, delay);
    }
}
var count = 1;
document.body.onresize = debounce(function(){
  console.log(count++);
}, 1000);
  • 两种版本的结合
function debounce(fn, delay, immediate) {
    let timer = null;
    return function() {
        clearTimeout(timer);
        let context = this;
        let args = arguments;
        if (!timer && immediate) {
            fn.apply(context, args);
        }
        timer = setTimeout(function() {
            timer = null;
            if (!immediate) {
                fn.apply(context, args);
            }
        }, delay);
    }
}

ASCII, Unicode和utf8,utf-16的区别

ASCII和unicode都是字符集,ASCII用8位二进制(开头第一个位置为0)来标识英文字母和常用的字符,这种标识方式对英文字符是够用的,但对其他的文字比如法语,中文,韩文等不够用。于是128之后的数字(将八位的第一位设成1)各个国家用来标识自己的字符,这样会导致同样的数字标识的字符并不一样,就出现了Unicode。

Unicode用数字对应每个字符,它本身只规定了每个字符对应的数字是多少,但不规定这些字符如何存储。

utf8和utf-16就是用来将这些字符对应的数字转换成二进制存储在计算机上,utf8和uft-16使用的编码方式不一样。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.