1,arguments
var length = 10;
function fn() {
alert(this.length);
}
var obj = {
length: 5,
method: function (fn) {
fn(); //?
arguments[0](); //?
},
fn: function() {
console.log(this.length);
}
};
obj.method(fn);
obj.fn();
arguments[0](); == arguments.fn();
2,字符串原型
var a = "123";
a.duplicate(); // "123123"
String.prototype.duplicate = () => {
return this + this;
}
null - (-1) // ? 1
3,css line-height區(qū)別
line-height:26px;
line-height:1.5;
line-height:150%;
line-height:1.5rem;
4,字符模版
const template = "My name is ${name},I'm from ${city}";
const result = sprintf(template, {
name: 'Zhang',
city: 'FuJian',
});
console.log(result); // My name is Zhang,I'm from FuJian
const sprintf = (str, data) => (
Object.keys(data).reduce((prev, cur) => {
let reg = new RegExp('\\$\\{' + cur + '\\}', 'g');
return prev.replace(reg, data[cur]);
}, str);
);
5,節(jié)流
// func是用戶傳入需要防抖的函數(shù)
// wait是等待時(shí)間
const throttle = (func, wait = 50) => {
// 上一次執(zhí)行該函數(shù)的時(shí)間
let lastTime = 0
return function(...args) {
// 當(dāng)前時(shí)間
let now = +new Date()
// 將當(dāng)前時(shí)間和上一次執(zhí)行函數(shù)時(shí)間對比
// 如果差值大于設(shè)置的等待時(shí)間就執(zhí)行函數(shù)
if (now - lastTime > wait) {
lastTime = now
func.apply(this, args)
}
}
}
6,防抖
// func是用戶傳入需要防抖的函數(shù)
// wait是等待時(shí)間
const debounce = (func, wait = 50) => {
// 緩存一個(gè)定時(shí)器id
let timer = 0
// 這里返回的函數(shù)是每次用戶實(shí)際調(diào)用的防抖函數(shù)
// 如果已經(jīng)設(shè)定過定時(shí)器了就清空上一次的定時(shí)器
// 開始一個(gè)新的定時(shí)器,延遲執(zhí)行用戶傳入的方法
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
if([]==false){console.log(1)}; //true
if({}==false){console.log(2)};
if([]){console.log(3)} //true
if([1]==[1]){console.log(4)}
var foo={};
var F = function(){};
Object.prototype.a = 'a';
Function.prototype.b='b';
console.log(foo.a) ==>a;
console.log(foo.b) ==>undefined;
console.log(F.a)==>a;
console.log(F.b)==>b;
7,作用域
var num1 = 55;
var num2 = 66;
function f1(num,num1) {
num = 100;
num1 = 100;
num2 = 100;
console.log(num);
console.log(num1);
console.log(num2);
}
f1(num1, num2);
console.log(num);
console.log(num1);
console.log(num2);
8,值類型和引用類型的傳遞
function Person(name,age,salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
function f2(person) {
person.name = 'ls';
person = new Person('aa',18,10000);
}
var p = new Person('zs',18,20000);
console.log(p.name);
f2(p);
console.log(p.name)
9,原型面試題
function F(){};
Object.prototype.a=function(){
console.log('a')
}
Function.prototype.b=function(){
console.log('b')
}
var f= new F();
f.a();
f.b();
F.a();
F.b();
function A(){}
A.prototype.n=1;
var b = new A();
A.prototype={
n:2,
m:3
}
var c= new A();
console.log(b.n,b.m,c.n,c.m)
10,MVVM
// 觀察者 (發(fā)布訂閱) 觀察者 被觀察者
class Dep {
constructor(){
this.subs = []; // 存放所有的watcher
}
// 訂閱
addSub(watcher){ // 添加 watcher
this.subs.push(watcher);
}
// 發(fā)布
notify(){
this.subs.forEach(watcher=>watcher.update());
}
}
// new Watcher
class Watcher{
constructor(vm,expr,cb){
this.vm = vm;
this.expr = expr;
this.cb = cb;
// 默認(rèn)先存放一個(gè)老值
this.oldValue = this.get();
}
get(){ // vm.$data.school vm.$data.school.name
Dep.target = this; // 先把自己放在this上
// 取值 把這個(gè)觀察者 和數(shù)據(jù)關(guān)聯(lián)起來
let value = CompileUtil.getVal(this.vm,this.expr);
Dep.target = null; // 不取消 任何值取值 都會添加watcher
return value;
}
update(){ // 更新操作 數(shù)據(jù)變化后 會調(diào)用觀察者的update方法
let newVal = CompileUtil.getVal(this.vm,this.expr);
if(newVal !== this.oldValue){
this.cb(newVal);
}
}
}
// vm.$watch(vm,'school.name',(newVal)=>{
// })
class Observer{ // 實(shí)現(xiàn)數(shù)據(jù)劫持功能
constructor(data){
this.observer(data);
}
observer(data){
// 如果是對象才觀察
if(data && typeof data == 'object'){
// 如果是對象
for(let key in data){
this.defineReactive(data,key,data[key]);
}
}
}
defineReactive(obj,key,value){
this.observer(value); // school :[watcher,watcher] b:[watcher]
let dep = new Dep() // 給每一個(gè)屬性 都加上一個(gè)具有發(fā)布訂閱的功能
Object.defineProperty(obj,key,{
get(){
// 創(chuàng)建watcher時(shí) 會取到對應(yīng)的內(nèi)容,并且把watcher放到了全局上
Dep.target && dep.addSub(Dep.target);
return value;
},
set:(newVal)=>{ // {school:{name:'珠峰'}} school ={}
if(newVal != value){
this.observer(newVal);
value = newVal;
dep.notify();
}
}
});
}
}
// 基類 調(diào)度
class Compiler{
constructor(el,vm){
// 判斷el屬性 是不是一個(gè)元素 如果不是元素 那就獲取他
this.el = this.isElementNode(el)?el:document.querySelector(el);
// 把當(dāng)前節(jié)點(diǎn)中的元素 獲取到 放到內(nèi)存中
this.vm = vm;
let fragment = this.node2fragment(this.el);
// 把節(jié)點(diǎn)中的內(nèi)容進(jìn)行替換
// 編譯模版 用數(shù)據(jù)編譯
this.compile(fragment);
// 把內(nèi)容在塞到頁面中
this.el.appendChild(fragment);
}
isDirective(attrName){
return attrName.startsWith('v-');
}
// 編譯元素的
compileElement(node){
let attributes = node.attributes; // 類數(shù)組
[...attributes].forEach(attr=>{ // type="text" v-model="school.name"
let {name,value:expr} = attr; // v-model="school.name"
// 判斷是不是指令 //v-
if(this.isDirective(name)){ // v-model v-html v-bind
let [,directive] = name.split('-'); // v-on:click
let [directiveName,eventName] =directive.split(':');
// 需要調(diào)用不同的指令來處理
CompileUtil[directiveName](node,expr,this.vm,eventName);
}
})
}
// 編譯文本的
compileText(node){ // 判斷當(dāng)前文本節(jié)點(diǎn)中內(nèi)容是否包含 {{xxx}} {{aaa}}
let content = node.textContent;
if(/\{\{(.+?)\}\}/.test(content)){
// 文本節(jié)點(diǎn)
CompileUtil['text'](node,content,this.vm); // {{a}} {}
}
}
// 核心的編譯方法
compile(node){ // 用來編譯內(nèi)存中的dom節(jié)點(diǎn)
let childNodes = node.childNodes;
[...childNodes].forEach(child=>{
if(this.isElementNode(child)){
this.compileElement(child);
// 如果是元素的話 需要把自己傳進(jìn)去 在去遍歷子節(jié)點(diǎn)
this.compile(child);
}else{
this.compileText(child);
}
});
}
// 把節(jié)點(diǎn)移動到內(nèi)存中
node2fragment(node){
// 創(chuàng)建一個(gè)文檔碎片
let fragment = document.createDocumentFragment();
let firstChild;
while(firstChild = node.firstChild){
// appendChild具有移動性
fragment.appendChild(firstChild);
}
return fragment
}
isElementNode(node){ // 是不是元素節(jié)點(diǎn)
return node.nodeType === 1;
}
}
// 3月16日開班
CompileUtil = {
// 根據(jù)表達(dá)式取到對應(yīng)的數(shù)據(jù)
getVal(vm,expr){ // vm.$data 'school' [school,name]
return expr.split('.').reduce((data,current)=>{
return data[current];
},vm.$data);
},
setValue(vm,expr,value){ //vm.$data 'school.name' = 姜文
expr.split('.').reduce((data,current,index,arr)=>{
if(index == arr.length-1){
return data[current] = value;
}
return data[current];
},vm.$data);
},
// 解析v-model這個(gè)指令
model(node,expr,vm){ // node是節(jié)點(diǎn) expr 是表達(dá)式 vm是當(dāng)前實(shí)例 school.name vm.$data
// 給輸入框賦予value屬性 node.value = xxx
let fn = this.updater['modelUpdater'];
new Watcher(vm,expr,(newVal)=>{ // 給輸入框加一個(gè)觀察者 如果稍后數(shù)據(jù)更新了會觸發(fā)此方法,會拿新值 給輸入框賦予值
fn(node,newVal);
});
node.addEventListener('input',(e)=>{
let value = e.target.value; // 獲取用戶輸入的內(nèi)容
this.setValue(vm,expr,value);
})
let value = this.getVal(vm,expr); // 珠峰
fn(node,value);
},
html(node,expr,vm){ // v-html="message"
let fn = this.updater['htmlUpdater'];
new Watcher(vm,expr,(newVal)=>{
console.log(newVal)
fn(node,newVal);
});
let value = this.getVal(vm,expr); // 珠峰
fn(node,value);
},
getContentValue(vm,expr){
// 遍歷表達(dá)式 將內(nèi)容 重新替換成一個(gè)完整的內(nèi)容 返還回去
return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
return this.getVal(vm,args[1]);
})
},
on(node,expr,vm,eventName){ // v-on:click="change" expr
node.addEventListener(eventName,(e)=>{
vm[expr].call(vm,e); // this.change
})
},
text(node,expr,vm){ // expr => 珠峰 {} {{c}} => a b
let fn = this.updater['textUpdater'];
let content = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
// 給表達(dá)式每{{}} 都加上觀察者
new Watcher(vm,args[1],()=>{
fn(node,this.getContentValue(vm,expr)); // 返回了一個(gè)全的字符串
});
return this.getVal(vm,args[1]);
});
fn(node,content);
},
updater:{
htmlUpdater(node,value){ // xss攻擊
node.innerHTML = value;
},
// 把數(shù)據(jù)插入到節(jié)點(diǎn)中
modelUpdater(node,value){
node.value = value;
},
// 處理文本節(jié)點(diǎn)的
textUpdater(node,value){
node.textContent = value;
}
}
}
class Vue{
constructor(options){
// this.$el $data $options
this.$el = options.el;
this.$data = options.data;
let computed = options.computed;
let methods = options.methods;
// 這個(gè)根元素 存在 編譯模板
if(this.$el){
// 把數(shù)據(jù) 全部轉(zhuǎn)化成用Object.defineProperty來定義
new Observer(this.$data);
// 把數(shù)據(jù)獲取操作 vm上的取值操作 都代理到 vm.$data
// {{getNewName}} reduce vm.$data.getNeName
for(let key in computed){ // 有依賴關(guān)系 數(shù)據(jù)
Object.defineProperty(this.$data,key,{
get:()=>{
return computed[key].call(this);
}
})
};
for(let key in methods){
Object.defineProperty(this,key,{
get(){
return methods[key]
}
})
}
this.proxyVm(this.$data);
new Compiler(this.$el,this);
}
}
//backbone set() get()
proxyVm(data){
for(let key in data){ // {school:{name,age}}
Object.defineProperty(this,key,{ // 實(shí)現(xiàn)可以通過vm取到對應(yīng)的內(nèi)容
get(){
return data[key]; // 進(jìn)行了轉(zhuǎn)化操作
},
set(newVal){ // 設(shè)置代理方法
data[key] = newVal;
}
})
}
}
}
11,深拷貝
function copy(obj) {
var vv = obj instanceof Array ? []:{};
for(var i in obj) {
if(typeof obj[i] == 'object'){
vv[i] = copy(obj[i])
}else {
vv[i] = obj[i]
}
}
return vv
}
//考慮null
function copy(obj) {
var vv = null;
if(typeof obj == 'object' && obj !== null) {
vv = obj instanceof Array ? [] : {};
for(var i in obj) {
vv[i] = copy(obj[i])
}
}else {
vv = obj
}
return vv
}
12,函數(shù)調(diào)用
function Foo(){
getName = function(){
console.log(1)
}
return this;
}
Foo.getName = function(){
console.log(2)
}
Foo.prototype.getName = function(){
console.log(3)
}
var getName = function(){
console.log(4)
}
function getName(){
console.log(5)
}
// ouput:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
13,觀察者模式
class Subject{
constructor(){
this.subs = [];
}
subscribe(sub) {
this.subs.push(sub)
}
unsubscribe(sub) {
const index = this.subs.indexOf(sub);
if(index > -1){
this.subs.splice(index,1);
}
}
fire() {
this.subs.forEach(sub=>{
sub.notify()
})
}
}
class Observer {
constructor(data){
this.data
}
notify(){
console.log(this.data)
}
}
let subject = new Subject()
let ob1 = new Observer('hello')
let ob2 = new Observer('world')
subject.subscribe(ob1)
subject.subscribe(ob2)
subject.fire()
14,發(fā)布訂閱模式
class EventChannel {
constructor() {
// 主題
this.subjects = {}
}
hasSubject(subject) {
return this.subjects[subject] ? true : false
}
/**
* 訂閱的主題
* @param {String} subject 主題
* @param {Function} callback 訂閱者
*/
on(subject, callback) {
if (!this.hasSubject(subject)) {
this.subjects[subject] = []
}
this.subjects[subject].push(callback)
}
/**
* 取消訂閱
*/
off(subject, callback) {
if (!this.hasSubject(subject)) {
return
}
const callbackList = this.subjects[subject]
const index = callbackList.indexOf(callback)
if (index > -1) {
callbackList.splice(index, 1)
}
}
/**
* 發(fā)布主題
* @param {String} subject 主題
* @param {Argument} data 參數(shù)
*/
emit(subject, ...data) {
if (!this.hasSubject(subject)) {
return
}
this.subjects[subject].forEach(callback => {
callback(...data)
})
}
}
const channel = new EventChannel()
channel.on('update', function(data) {
console.log(`update value: ${data}`)
})
channel.emit('update', 123)
15,柯里化函數(shù)
柯里化的定義:在數(shù)學(xué)和計(jì)算機(jī)科學(xué)中,柯里化是一種將使用多個(gè)參數(shù)的一個(gè)函數(shù)轉(zhuǎn)換成一系列使用一個(gè)參數(shù)的函數(shù)的技術(shù)。
function curry (fn, currArgs) {
return function() {
let args = [].slice.call(arguments);
// 首次調(diào)用時(shí),若未提供最后一個(gè)參數(shù)currArgs,則不用進(jìn)行args的拼接
if (currArgs !== undefined) {
args = args.concat(currArgs);
}
// 遞歸調(diào)用
if (args.length < fn.length) {
return curry(fn, args);
}
// 遞歸出口
return fn.apply(null, args);
}
}
測試一下
柯里化的目的是,減少代碼冗余,以及增加代碼的可讀性
function sum(a, b, c) {
console.log(a + b + c);
}
const fn = curry(sum);
fn(1, 2, 3); // 6
fn(1, 2)(3); // 6
fn(1)(2, 3); // 6
fn(1)(2)(3); // 6
都能輸出 6 了,搞定!