乐趣区

关于前端:web前端面试题及答案2023

组件之间通信

  • 父子组件通信
  • 自定义事件
  • redux 和 context

context 如何使用

  • 父组件向其下所有子孙组件传递信息
  • 如一些简略的信息:主题、语言
  • 简单的公共信息用 redux

在跨层级通信中,次要分为一层或多层的状况

  • 如果只有一层,那么依照 React 的树形构造进行分类的话,次要有以下三种状况:父组件向子组件通信 子组件向父组件通信 以及 平级的兄弟组件间相互通信
  • 在父与子的状况下,因为 React 的设计实际上就是传递 Props 即可。那么场景体现在容器组件与展现组件之间,通过 Props 传递 state,让展现组件受控。
  • 在子与父的状况下 ,有两种形式,别离是回调函数与实例函数。回调函数,比方输入框向父级组件返回输出内容,按钮向父级组件传递点击事件等。实例函数的状况有些特地,次要是在父组件中 通过 React 的 ref API 获取子组件的实例 ,而后是 通过实例调用子组件的实例函数。这种形式在过来常见于 Modal 框的显示与暗藏
  • 多层级间的数据通信,有两种状况。第一种是一个容器中蕴含了多层子组件,须要最底部的子组件与顶部组件进行通信。在这种状况下,如果一直透传 Props 或回调函数,不仅代码层级太深,后续也很不好保护。第二种是两个组件不相干,在整个 React 的组件树的两侧,齐全不相交。那么基于多层级间的通信个别有三个计划。

    • 第一个是应用 React 的 Context API,最常见的用处是做语言包国际化
    • 第二个是应用全局变量与事件。
    • 第三个是应用状态治理框架,比方 Flux、Redux 及 Mobx。长处是因为引入了状态治理,使得我的项目的开发模式与代码构造得以束缚,毛病是学习老本绝对较高

代码输入后果

function runAsync (x) {const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
    return p
}

Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))

输入后果如下:

1
2
3
[1, 2, 3]

首先,定义了一个 Promise,来异步执行函数 runAsync,该函数传入一个值 x,而后距离一秒后打印出这个 x。

之后再应用 Promise.all 来执行这个函数,执行的时候,看到一秒之后输入了 1,2,3,同时输入了数组[1, 2, 3],三个函数是同步执行的,并且在一个回调函数中返回了所有的后果。并且后果和函数的执行程序是统一的。

过程和线程的区别

  • 过程能够看做独立利用,线程不能
  • 资源:过程是 cpu 资源分配的最小单位(是能领有资源和独立运行的最小单位);线程是 cpu 调度的最小单位(线程是建设在过程的根底上的一次程序运行单位,一个过程中能够有多个线程)。
  • 通信方面:线程间能够通过间接共享同一过程中的资源,而过程通信须要借助 过程间通信。
  • 调度:过程切换比线程切换的开销要大。线程是 CPU 调度的根本单位,线程的切换不会引起过程切换,但某个过程中的线程切换到另一个过程中的线程时,会引起过程切换。
  • 零碎开销:因为创立或撤销过程时,零碎都要为之调配或回收资源,如内存、I/O 等,其开销远大于创立或撤销线程时的开销。同理,在进行过程切换时,波及以后执行过程 CPU 环境还有各种各样状态的保留及新调度过程状态的设置,而线程切换时只需保留和设置大量寄存器内容,开销较小。

深拷贝

实现一:不思考 Symbol

function deepClone(obj) {if(!isObject(obj)) return obj;
    let newObj = Array.isArray(obj) ? [] : {};
    // for...in 只会遍历对象本身的和继承的可枚举的属性(不含 Symbol 属性)for(let key in obj) {// obj.hasOwnProperty() 办法只思考对象本身的属性
        if(obj.hasOwnProperty(key)) {newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key];
        }
    }
    return newObj;
}

实现二:思考 Symbol

// hash 作为一个查看器,防止对象深拷贝中呈现环援用,导致爆栈
function deepClone(obj, hash = new WeakMap()) {if(!isObject(obj)) return obj;
    // 查看是有存在雷同的对象在之前拷贝过,有则返回之前拷贝后存于 hash 中的对象
    if(hash.has(obj)) return hash.get(obj);
    let newObj = Array.isArray(obj) ? [] : {};
    // 备份存在 hash 中,newObj 目前是空对象、数组。前面会对属性进行追加,这里存的值是对象的栈
    hash.set(obj, newObj);
    // Reflect.ownKeys 返回一个数组,蕴含对象本身的(不含继承的)所有键名,不论键名是 Symbol 或字符串,也不论是否可枚举。Reflect.ownKeys(obj).forEach(key => {
        // 属性值如果是对象,则进行递归深拷贝,否则间接拷贝
        newObj[key] = isObject(obj[key]) ? deepClone(obj[key], hash) : obj[key];
    });
    return newObj;
}

Vue 的生命周期是什么 每个钩子外面具体做了什么事件

Vue 实例有⼀个残缺的⽣命周期,也就是从开始创立、初始化数据、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是 Vue 的⽣命周期。1、beforeCreate(创立前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能拜访到 data、computed、watch、methods 上的办法和数据。2、created(创立后):实例创立实现,实例上配置的 options 包含 data、computed、watch、methods 等都配置实现,然而此时渲染得节点还未挂载到 DOM,所以不能拜访到 `$el` 属性。3、beforeMount(挂载前):在挂载开始之前被调用,相干的 render 函数首次被调用。实例已实现以下的配置:编译模板,把 data 外面的数据和模板生成 html。此时还没有挂载 html 到页面上。4、mounted(挂载后):在 el 被新创建的 vm.$el 替换,并挂载到实例下来之后调用。实例已实现以下的配置:用下面编译好的 html 内容替换 el 属性指向的 DOM 对象。实现模板中的 html 渲染到 html 页面中。此过程中进行 ajax 交互。5、beforeUpdate(更新前):响应式数据更新时调用,此时尽管响应式数据更新了,然而对应的实在 DOM 还没有被渲染。6、updated(更新后):在因为数据更改导致的虚构 DOM 从新渲染和打补丁之后调用。此时 DOM 曾经依据响应式数据的变动更新了。调用时,组件 DOM 曾经更新,所以能够执行依赖于 DOM 的操作。然而在大多数状况下,应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。7、beforeDestroy(销毁前):实例销毁之前调用。这一步,实例依然齐全可用,`this` 仍能获取到实例。8、destroyed(销毁后):实例销毁后调用,调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。另外还有 `keep-alive` 独有的生命周期,别离为 `activated` 和 `deactivated`。用 `keep-alive` 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 `deactivated` 钩子函数,命中缓存渲染后会执行 `activated` 钩子函数。

IndexedDB 有哪些特点?

IndexedDB 具备以下特点:

  • 键值对贮存:IndexedDB 外部采纳对象仓库(object store)存放数据。所有类型的数据都能够间接存入,包含 JavaScript 对象。对象仓库中,数据以 ” 键值对 ” 的模式保留,每一个数据记录都有对应的主键,主键是举世无双的,不能有反复,否则会抛出一个谬误。
  • 异步:IndexedDB 操作时不会锁死浏览器,用户仍然能够进行其余操作,这与 LocalStorage 造成比照,后者的操作是同步的。异步设计是为了避免大量数据的读写,拖慢网页的体现。
  • 反对事务:IndexedDB 反对事务(transaction),这意味着一系列操作步骤之中,只有有一步失败,整个事务就都勾销,数据库回滚到事务产生之前的状态,不存在只改写一部分数据的状况。
  • 同源限度: IndexedDB 受到同源限度,每一个数据库对应创立它的域名。网页只能拜访本身域名下的数据库,而不能拜访跨域的数据库。
  • 贮存空间大:IndexedDB 的贮存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有下限。
  • 反对二进制贮存:IndexedDB 不仅能够贮存字符串,还能够贮存二进制数据(ArrayBuffer 对象和 Blob 对象)。

AJAX

const getJSON = function(url) {return new Promise((resolve, reject) => {const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
        xhr.open('GET', url, false);
        xhr.setRequestHeader('Accept', 'application/json');
        xhr.onreadystatechange = function() {if (xhr.readyState !== 4) return;
            if (xhr.status === 200 || xhr.status === 304) {resolve(xhr.responseText);
            } else {reject(new Error(xhr.responseText));
            }
        }
        xhr.send();})
}

实现数组原型办法

forEach

Array.prototype.forEach2 = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)  // this 就是以后的数组
    const len = O.length >>> 0  // 前面有解释
    let k = 0
    while (k < len) {if (k in O) {callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
}

O.length >>> 0 是什么操作?就是无符号右移 0 位,那有什么意义嘛?就是为了保障转换后的值为正整数。其实底层做了 2 层转换,第一是非 number 转成 number 类型,第二是将 number 转成 Uint32 类型

map

基于 forEach 的实现可能很容易写出 map 的实现:

- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.map2 = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
-   let k = 0
+   let k = 0, res = []
    while (k < len) {if (k in O) {-           callback.call(thisArg, O[k], k, O);
+           res[k] = callback.call(thisArg, O[k], k, O);
        }
        k++;
    }
+   return res
}

filter

同样,基于 forEach 的实现可能很容易写出 filter 的实现:

- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.filter2 = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
-   let k = 0
+   let k = 0, res = []
    while (k < len) {if (k in O) {-           callback.call(thisArg, O[k], k, O);
+           if (callback.call(thisArg, O[k], k, O)) {+               res.push(O[k])                
+           }
        }
        k++;
    }
+   return res
}

some

同样,基于 forEach 的实现可能很容易写出 some 的实现:

- Array.prototype.forEach2 = function(callback, thisArg) {+ Array.prototype.some2 = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
    let k = 0
    while (k < len) {if (k in O) {-           callback.call(thisArg, O[k], k, O);
+           if (callback.call(thisArg, O[k], k, O)) {
+               return true
+           }
        }
        k++;
    }
+   return false
}

reduce

Array.prototype.reduce2 = function(callback, initialValue) {if (this == null) {throw new TypeError('this is null or not defined')
    }
    if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function')
    }
    const O = Object(this)
    const len = O.length >>> 0
    let k = 0, acc

    if (arguments.length > 1) {acc = initialValue} else {
        // 没传入初始值的时候,取数组中第一个非 empty 的值为初始值
        while (k < len && !(k in O)) {k++}
        if (k > len) {throw new TypeError( 'Reduce of empty array with no initial value');
        }
        acc = O[k++]
    }
    while (k < len) {if (k in O) {acc = callback(acc, O[k], k, O)
        }
        k++
    }
    return acc
}

Promise.all

形容:所有 promise 的状态都变成 fulfilled,就会返回一个状态为 fulfilled 的数组(所有promisevalue)。只有有一个失败,就返回第一个状态为 rejectedpromise 实例的 reason

实现

Promise.all = function(promises) {return new Promise((resolve, reject) => {if(Array.isArray(promises)) {if(promises.length === 0) return resolve(promises);
            let result = [];
            let count = 0;
            promises.forEach((item, index) => {Promise.resolve(item).then(
                    value => {
                        count++;
                        result[index] = value;
                        if(count === promises.length) resolve(result);
                    }, 
                    reason => reject(reason)
                );
            })
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

一个 tcp 连贯能发几个 http 申请?

如果是 HTTP 1.0 版本协定,个别状况下,不反对长连贯,因而在每次申请发送结束之后,TCP 连贯即会断开,因而一个 TCP 发送一个 HTTP 申请,然而有一种状况能够将一条 TCP 连贯放弃在沉闷状态,那就是通过 Connection 和 Keep-Alive 首部,在申请头带上 Connection: Keep-Alive,并且能够通过 Keep-Alive 通用首部中指定的,用逗号分隔的选项调节 keep-alive 的行为,如果客户端和服务端都反对,那么其实也能够发送多条,不过此形式也有限度,能够关注《HTTP 权威指南》4.5.5 节对于 Keep-Alive 连贯的限度和规定。

而如果是 HTTP 1.1 版本协定,反对了长连贯,因而只有 TCP 连接不断开,便能够始终发送 HTTP 申请,继续一直,没有下限;同样,如果是 HTTP 2.0 版本协定,反对多用复用,一个 TCP 连贯是能够并发多个 HTTP 申请的,同样也是反对长连贯,因而只有一直开 TCP 的连贯,HTTP 申请数也是能够没有下限地继续发送

Promise.any

形容 :只有 promises 中有一个fulfilled,就返回第一个fulfilledPromise实例的返回值。

实现

Promise.any = function(promises) {return new Promise((resolve, reject) => {if(Array.isArray(promises)) {if(promises.length === 0) return reject(new AggregateError("All promises were rejected"));
            let count = 0;
            promises.forEach((item, index) => {Promise.resolve(item).then(value => resolve(value),
                    reason => {
                        count++;
                        if(count === promises.length) {reject(new AggregateError("All promises were rejected"));
                        };
                    }
                );
            })
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

具体阐明 Event loop

家喻户晓 JS 是门非阻塞单线程语言,因为在最后 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话,咱们在多个线程中解决 DOM 就可能会产生问题(一个线程中新加节点,另一个线程中删除节点),当然能够引入读写锁解决这个问题。

JS 在执行的过程中会产生执行环境,这些执行环境会被程序的退出到执行栈中。如果遇到异步的代码,会被挂起并退出到 Task(有多种 task)队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出须要执行的代码并放入执行栈中执行,所以实质上来说 JS 中的异步还是同步行为。

console.log('script start');

setTimeout(function() {console.log('setTimeout');
}, 0);

console.log('script end');

以上代码尽管 setTimeout 延时为 0,其实还是异步。这是因为 HTML5 标准规定这个函数第二个参数不得小于 4 毫秒,有余会主动减少。所以 setTimeout 还是会在 script end 之后打印。

不同的工作源会被调配到不同的 Task 队列中,工作源能够分为 微工作(microtask)和 宏工作(macrotask)。在 ES6 标准中,microtask 称为 jobs,macrotask 称为 task

console.log('script start');

setTimeout(function() {console.log('setTimeout');
}, 0);

new Promise((resolve) => {console.log('Promise')
    resolve()}).then(function() {console.log('promise1');
}).then(function() {console.log('promise2');
});

console.log('script end');
// script start => Promise => script end => promise1 => promise2 => setTimeout

以上代码尽管 setTimeout 写在 Promise 之前,然而因为 Promise 属于微工作而 setTimeout 属于宏工作,所以会有以上的打印。

微工作包含 process.nextTickpromiseObject.observeMutationObserver

宏工作包含 scriptsetTimeoutsetIntervalsetImmediateI/OUI rendering

很多人有个误区,认为微工作快于宏工作,其实是谬误的。因为宏工作中包含了 script,浏览器会先执行一个宏工作,接下来有异步代码的话就先执行微工作。

所以正确的一次 Event loop 程序是这样的

  1. 执行同步代码,这属于宏工作
  2. 执行栈为空,查问是否有微工作须要执行
  3. 执行所有微工作
  4. 必要的话渲染 UI
  5. 而后开始下一轮 Event loop,执行宏工作中的异步代码

通过上述的 Event loop 程序可知,如果宏工作中的异步代码有大量的计算并且须要操作 DOM 的话,为了更快的 界面响应,咱们能够把操作 DOM 放入微工作中。

Node 中的 Event loop

Node 中的 Event loop 和浏览器中的不雷同。

Node 的 Event loop 分为 6 个阶段,它们会依照程序重复运行

┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<──connections───     │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘
timer

timers 阶段会执行 setTimeoutsetInterval

一个 timer 指定的工夫并不是精确工夫,而是在达到这个工夫后尽快执行回调,可能会因为零碎正在执行别的事务而提早。

上限的工夫有一个范畴:[1, 2147483647],如果设定的工夫不在这个范畴,将被设置为 1。

I/O

I/O 阶段会执行除了 close 事件,定时器和 setImmediate 的回调

idle, prepare

idle, prepare 阶段外部实现

poll

poll 阶段很重要,这一阶段中,零碎会做两件事件

  1. 执行到点的定时器
  2. 执行 poll 队列中的事件

并且当 poll 中没有定时器的状况下,会发现以下两件事件

  • 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者零碎限度
  • 如果 poll 队列为空,会有两件事产生

    • 如果有 setImmediate 须要执行,poll 阶段会进行并且进入到 check 阶段执行 setImmediate
    • 如果没有 setImmediate 须要执行,会期待回调被退出到队列中并立刻执行回调

如果有别的定时器须要被执行,会回到 timer 阶段执行回调。

check

check 阶段执行 setImmediate

close callbacks

close callbacks 阶段执行 close 事件

并且在 Node 中,有些状况下的定时器执行程序是随机的

setTimeout(() => {console.log('setTimeout');
}, 0);
setImmediate(() => {console.log('setImmediate');
})
// 这里可能会输入 setTimeout,setImmediate
// 可能也会相同的输入,这取决于性能
// 因为可能进入 event loop 用了不到 1 毫秒,这时候会执行 setImmediate
// 否则会执行 setTimeout

当然在这种状况下,执行程序是雷同的

var fs = require('fs')

fs.readFile(__filename, () => {setTimeout(() => {console.log('timeout');
    }, 0);
    setImmediate(() => {console.log('immediate');
    });
});
// 因为 readFile 的回调在 poll 中执行
// 发现有 setImmediate,所以会立刻跳到 check 阶段执行回调
// 再去 timer 阶段执行 setTimeout
// 所以以上输入肯定是 setImmediate,setTimeout

下面介绍的都是 macrotask 的执行状况,microtask 会在以上每个阶段实现后立刻执行。

setTimeout(()=>{console.log('timer1')

    Promise.resolve().then(function() {console.log('promise1')
    })
}, 0)

setTimeout(()=>{console.log('timer2')

    Promise.resolve().then(function() {console.log('promise2')
    })
}, 0)

// 以上代码在浏览器和 node 中打印状况是不同的
// 浏览器中打印 timer1, promise1, timer2, promise2
// node 中打印 timer1, timer2, promise1, promise2

Node 中的 process.nextTick 会先于其余 microtask 执行。

setTimeout(() => {console.log("timer1");

  Promise.resolve().then(function() {console.log("promise1");
  });
}, 0);

process.nextTick(() => {console.log("nextTick");
});
// nextTick, timer1, promise1

对浏览器内核的了解

浏览器内核次要分成两局部:

  • 渲染引擎的职责就是渲染,即在浏览器窗口中显示所申请的内容。默认状况下,渲染引擎能够显示 html、xml 文档及图片,它也能够借助插件显示其余类型数据,例如应用 PDF 阅读器插件,能够显示 PDF 格局。
  • JS 引擎:解析和执行 javascript 来实现网页的动态效果。

最开始渲染引擎和 JS 引擎并没有辨别的很明确,起初 JS 引擎越来越独立,内核就偏向于只指渲染引擎。

插入排序 – 工夫复杂度 n^2

题目形容: 实现一个插入排序

实现代码如下:

function insertSort(arr) {for (let i = 1; i < arr.length; i++) {
    let j = i;
    let target = arr[j];
    while (j > 0 && arr[j - 1] > target) {arr[j] = arr[j - 1];
      j--;
    }
    arr[j] = target;
  }
  return arr;
}
// console.log(insertSort([3, 6, 2, 4, 1]));

AJAX

实现:利用 XMLHttpRequest

// get
const getJSON = (url) => {return new Promise((resolve, reject) => {let xhr = new XMLHttpRequest();
        // open 办法用于指定 HTTP 申请的参数: method, url, async(是否异步,默认 true)xhr.open("GET", url, false);
        xhr.setRequestHeader('Content-Type', 'application/json');
        // onreadystatechange 属性指向一个监听函数。// readystatechange 事件产生时(实例的 readyState 属性变动),就会执行这个属性。xhr.onreadystatechange = function(){
            // 4 示意服务器返回的数据曾经齐全接管,或者本次接管曾经失败
            if(xhr.readyState !== 4) return;
            // 申请胜利,基本上只有 2xx 和 304 的状态码,示意服务器返回是失常状态
            if(xhr.status === 200 || xhr.status === 304) {
                // responseText 属性返回从服务器接管到的字符串
                resolve(xhr.responseText);
            }
            // 申请失败
            else {reject(new Error(xhr.responseText));
            }
        }
        xhr.send();});
}

// post
const postJSON = (url, data) => {return new Promise((resolve, reject) => {let xhr = new XMLHttpRequest();
        xhr.open("POST", url);
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        xhr.onreadystatechange = function(){if(xhr.readyState !== 4) return;
            if(xhr.status === 200 || xhr.status === 304) {resolve(xhr.responseText);
            }
            else {reject(new Error(xhr.responseText));
            }
        }
        xhr.send(data);
    });
}

Promise.race

形容 :只有promises 中有一个率先扭转状态,就返回这个率先扭转的 Promise 实例的返回值。

实现

Promise.race = function(promises){return new Promise((resolve, reject) => {if(Array.isArray(promises)) {if(promises.length === 0) return resolve(promises);
            promises.forEach((item) => {Promise.resolve(item).then(value => resolve(value), 
                    reason => reject(reason)
                );
            })
        }
        else return reject(new TypeError("Argument is not iterable"));
    });
}

单例模式

用意:保障一个类仅有一个实例,并提供一个拜访它的全局拜访点。

次要解决:一个全局应用的类频繁地创立与销毁。

何时应用:当您想管制实例数目,节俭系统资源的时候。

如何解决:判断零碎是否曾经有这个单例,如果有则返回,如果没有则创立。

实现

var Singleton = (function() {
    // 如果在外部申明 SingletonClass 对象,则无奈在内部间接调用
    var SingletonClass = function() {}; 
    var instance;
    return function() {
        // 如果已存在,则返回 instance
        if(instance) return instance;
        // 如果不存在,则 new 一个 SingletonClass 对象
        instance = new SingletonClass();
        return instance;
    }
})();

// 测试
var a = new Singleton();
var b = new Singleton();
console.log(a === b);  // true

代码输入问题

function Parent() {
    this.a = 1;
    this.b = [1, 2, this.a];
    this.c = {demo: 5};
    this.show = function () {console.log(this.a , this.b , this.c.demo);
    }
}

function Child() {
    this.a = 2;
    this.change = function () {this.b.push(this.a);
        this.a = this.b.length;
        this.c.demo = this.a++;
    }
}

Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;
parent.show();
child1.show();
child2.show();
child1.change();
child2.change();
parent.show();
child1.show();
child2.show();

输入后果:

parent.show(); // 1  [1,2,1] 5

child1.show(); // 11 [1,2,1] 5
child2.show(); // 12 [1,2,1] 5

parent.show(); // 1 [1,2,1] 5

child1.show(); // 5 [1,2,1,11,12] 5

child2.show(); // 6 [1,2,1,11,12] 5

这道题目值得神帝,他波及到的知识点很多,例如 this 的指向、原型、原型链、类的继承、数据类型 等。

解析:

  1. parent.show(),能够间接取得所需的值,没啥好说的;
  2. child1.show(),Child的构造函数本来是指向 Child 的,题目显式将 Child 类的原型对象指向了 Parent 类的一个实例,须要留神 Child.prototype 指向的是 Parent 的实例 parent,而不是指向Parent 这个类。
  3. child2.show(),这个也没啥好说的;
  4. parent.show(),parent是一个 Parent 类的实例,Child.prorotype指向的是 Parent 类的另一个实例,两者在堆内存中互不影响,所以上述操作不影响 parent 实例,所以输入后果不变;
  5. child1.show(),child1执行了 change() 办法后,产生了怎么的变动呢?
  6. this.b.push(this.a),因为 this 的动静指向个性,this.b 会指向 Child.prototype 上的 b 数组,this.a 会指向 child1a属性, 所以 Child.prototype.b 变成了[1,2,1,11];
  7. this.a = this.b.length,这条语句中 this.athis.b的指向与上一句统一,故后果为 child1.a 变为4;
  8. this.c.demo = this.a++,因为 child1 本身属性并没有 c 这个属性,所以此处的 this.c 会指向 Child.prototype.cthis.a 值为 4,为原始类型,故赋值操作时会间接赋值,Child.prototype.c.demo 的后果为 4,而this.a 随后自增为5(4 + 1 = 5)。
  9. child2执行了 change() 办法, 而 child2child1均是 Child 类的实例,所以他们的原型链指向同一个原型对象 Child.prototype, 也就是同一个parent 实例,所以 child2.change() 中所有影响到原型对象的语句都会影响 child1 的最终输入后果。
  10. this.b.push(this.a),因为 this 的动静指向个性,this.b 会指向 Child.prototype 上的 b 数组,this.a 会指向 child2a属性, 所以 Child.prototype.b 变成了[1,2,1,11,12];
  11. this.a = this.b.length,这条语句中 this.athis.b的指向与上一句统一,故后果为 child2.a 变为5;
  12. this.c.demo = this.a++,因为 child2 本身属性并没有 c 这个属性,所以此处的 this.c 会指向 Child.prototype.c,故执行后果为Child.prototype.c.demo 的值变为 child2.a 的值 5,而child2.a 最终自增为6(5 + 1 = 6)。

代码输入后果

var obj = {say: function() {var f1 = () =>  {console.log("1111", this);
     }
     f1();},
   pro: {getPro:() =>  {console.log(this);
     }
   }
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();

输入后果:

1111 window 对象
1111 obj 对象
window 对象

解析:

  1. o(),o 是在全局执行的,而 f1 是箭头函数,它是没有绑定 this 的,它的 this 指向其父级的 this,其父级 say 办法的 this 指向的是全局作用域,所以会打印出 window;
  2. obj.say(),谁调用 say,say 的 this 就指向谁,所以此时 this 指向的是 obj 对象;
  3. obj.pro.getPro(),咱们晓得,箭头函数时不绑定 this 的,getPro 处于 pro 中,而对象不形成独自的作用域,所以箭头的函数的 this 就指向了全局作用域 window。

首屏和白屏工夫如何计算

首屏工夫的计算,能够由 Native WebView 提供的相似 onload 的办法实现,在 ios 下对应的是 webViewDidFinishLoad,在 android 下对应的是 onPageFinished 事件。

白屏的定义有多种。能够认为“没有任何内容”是白屏,能够认为“网络或服务异样”是白屏,能够认为“数据加载中”是白屏,能够认为“图片加载不进去”是白屏。场景不同,白屏的计算形式就不雷同。

办法 1:当页面的元素数小于 x 时,则认为页面白屏。比方“没有任何内容”,能够获取页面的 DOM 节点数,判断 DOM 节点数少于某个阈值 X,则认为白屏。办法 2:当页面呈现业务定义的错误码时,则认为是白屏。比方“网络或服务异样”。办法 3:当页面呈现业务定义的特征值时,则认为是白屏。比方“数据加载中”。

代码输入后果

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log(this.foo);  
        console.log(self.foo);  
        (function() {console.log(this.foo);  
            console.log(self.foo);  
        }());
    }
};
myObject.func();

输入后果:bar bar undefined bar

解析:

  1. 首先 func 是由 myObject 调用的,this 指向 myObject。又因为 var self = this; 所以 self 指向 myObject。
  2. 这个立刻执行匿名函数表达式是由 window 调用的,this 指向 window。立刻执行匿名函数的作用域处于 myObject.func 的作用域中,在这个作用域找不到 self 变量,沿着作用域链向上查找 self 变量,找到了指向 myObject 对象的 self。
退出移动版