原型是js中实现继承的基础,
通过__proto__(隐式原型)实现实例对象与原型对象之间的连接,直至null,这一条链叫做原型链。
取决于对象创建的实现方式,
var a = {
};
console.log(a.__proto__ === Object.prototype); // true
console.log(a.__proto__ === a.constructor.prototype); // true
var A = function() {
};
var a = new A();
console.log(a.__proto__ === A.prototype); // true
console.log(a.constructor === A); // true
var a1 = {
};
var a2 = Object.create(a1);
console.log(a2.__proto__ === a1); // true
console.log(a2.constructor === Object); // true
var a = {
age: 10};
Object.prototype.name = 'zhangsan';
console.log(a.name); // 会去原型上找,输出 zhangsan
a.name = 'lisi';
// 不会修改Prototype上的name,会在a上添加一个name属性
console.log(a); // {age: 10, name: 'lisi'}
console.log(Object.prototype.name); // zhangsan
深拷贝的方法:
function deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {
};
if (obj && typeof obj === 'object') {
for(key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === 'object') {
objClone[key] = deepClone(obj[key]);
} else {
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
function deepClone(obj) {
let _obj = JSON.stringify(obj);
let objClone = JSON.parse(_obj);
return objClone;
}
$.extend(deep, target, obj1, objN)
由于浏览器的同源策略导致的。只有当协议、域名、端口号三者完全一致时才是同源。
// 手写jsonp
function jsonp(url, data, cb) {
let dataString = url.indexOf('?') === -1 ? '?' : '&';
let callbackName = 'jsonpCallback';
url += `${
dataString}callback=${
callbackName}`;
if (data)
for(let i in data) {
url += `&${
i}=${
data[i]}`;
}
}
var script = document.createElement('script');
script.src = url;
window[callbackName] = (result) => {
delete window[callbackName];
document.body.removeChild(script);
cb(result);
};
script.onerror = function(error) {
document.body.removeChild(script);
};
document.body.appendChild(script);
}
// nginx配置
server {
listen 9090; // 监听9090端口
server_name localhost; // 设置域名
location ^~ /api {
proxy_pass http://localhost:9871;
}
// localhost:9090/api 这种请求都会转发到服务器地址
}
// a.html http://www.domain1.com/a.html
var crossDomain = function(url, cb) {
var state = 0;
var iframe = document.createElement('iframe');
// 加载跨域页面
iframe.src = url;
// 触发onload事件
iframe.onload = function() {
if (state === 0) {
// 第一次onload,留存数据于window.name,切换到同域页面
iframe.contentWindow.location = '代理页面地址,与a.html同域';
state = 1;
} else {
// 第二次onload
cb(iframe.contentWindow.name);
// 删除iframe
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
}
}
// b.html http://www.domain2.com/b.html
// 该页面设置window.name
window.name = '数据';
// http://www.domain1.com/a.html
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;">
var iframe = document.querySelector('#iframe');
iframe.onload = function() {
var data = {
};
// 向domain2传送跨域数据
iframe.contentWindow.postMessage(data, 'http://www.domain2.com');
}
// 接收domain2返回的数据
window.addEventListener('message', function(e) {
console.log(e.data);
}, false);
// http://www.domain2.com/b.html
// 接收domain1的数据
window.addEventListener('message', function(e) {
var data2 = e.data;
// 处理数据,然后发送至domain1
data2.property1 = 'domain2';
window.parent.postMessage(data2, 'http://www.domain1.com');
}, false);
与普通函数的不同点:
function newArr(arr) {
return Array.from(new Set(arr));
}
function newArr(arr) {
for(var i = 0; i < arr.length; i++) {
for(var j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
function newArr(arr) {
var newArr = [];
for(var i = 0; i < arr.length; i++) {
if(newArr.indexOf(arr[i]) === -1) {
newArr.push(arr[i]);
}
}
return newArr;
}
function newArr(arr) {
var newArr = arr.filter((item, index, self) => {
// 找在数组中第一次出现的元素
self.indexOf(item) === index;
});
}
已知对象的某一个属性id去重:
function newArr(objArr) {
var obj = {
};
var newObjArr = [];
for(var i = 0; i < objArr.length; i++) {
if(!obj[objArr[i].id]) {
newObjArr.push(objArr[i]);
obj[objArr[i].id] = true;
}
}
return newObjArr;
}
使用new操作符产生一个实例的过程:var p = new Person()
在new的执行过程中,
没有显式返回值,则返回this,即构造函数的实例对象。
function MyNew(func) {
return function() {
var obj = {
};
obj.constructor = func;
obj.__proto__ = func.prototype;
func.apply(obj, arguments);
return obj;
}
}
function Person(name, age) {
this.name = name;
this.age = age;
// 显式返回非引用类型
return 'test';
}
var p1 = new Person('zhangsan', 20); // p1是new出来的一个Person实例
var p2 = Person('zhangsan', 20);
console.log(p1.name); // zhangsan
console.log(p2.name); // undefined
console.log(p2); // test
function Person(name, age) {
var o = new Object();
o.name = name;
o.age = age;
this.test = 'test';
return o;
}
var p1 = new Person('zhangsan', 20);
var p2 = Person('zhangsan', 20);
// p1不是Person的实例,取不到实例上的test属性
console.log(p1.test); // undefined
console.log(p2.name); // zhangsan
改造构造函数使得直接调用也返回构造函数的实例:
function Person(name, age) {
// 判断是直接调用还是new调用
if(!(this instanceof Person)) {
var p1 = new Person(name, age);
return p1;
} else {
this.name = name;
this.age = age;
}
}
三个方法的作用是用来改变this的指向。
// 实现call
Function.prototype.call = function(context) {
context = context || window;
let args = [...arguments].slice(1);
context.fn = this;
let result = context.fn(...args);
delete context.fn;
return result;
}
// 实现apply
Function.prototype.apply = function(context) {
context = context || window;
context.fn = this;
let result;
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
delete context.fn;
return result;
}
Function.prototype.bind = function(context) {
context = context || window;
let self = this;
let args = [...arguments].slice(1);
return function() {
self.apply(context, args.concat(Array.prototype.slice.call(arguments)));
};
}
var one = function(){
console.log(this.x);
}
var two = {
x: 1
}
var three = {
x: 2
}
var fn = one.bind(two).bind(three);
fn(); // 1
let preBind = Function.prototype.bind;
Function.prototype.bind = function() {
var fn = typeof this.__bind__ === 'function' ? this.__bind__ : this;
var bindFn = preBind.apply(fn, arguments);
Object.defineProperty(bindFn, '__bind__', {
value: fn
});
return bindFn;
}
js执行前先进行‘预编译’,在预编译期间主要完成以下两件事:
变量的提升是在预编译阶段完成的。
只有var定义的变量会提升,ES6中的let和const声明的变量会产生块级作用域,形成暂时性死区。
function test() {
console.log(test);
let test = 10;
}
test(); // Cannot access 'test' before initialization
由于使用setTimeout实现动画效果可能会造成丢帧的现象(执行步调和屏幕刷新步调不一致),可以使用requestAnimationFrame。
var progress = 0;
function render() {
progress += 1;
if(progress < 100) {
// 动画未结束前,递归渲染
window.requestAnimationFrame(render);
}
}
// 第一帧渲染
window.requestAnimationFrame(render);
String.prototype.format = function() {
var args = [...arguments];
var self = this;
var reg = /{+(\d+)}+/g;
return self.replace(reg, function(matchStr, index) {
return typeof args[index] !== 'undefined' ? args[index] : '';
});
}
'hello {0}, welcome to {1}.'.format('Lily', 'BeiJing');
// hello Lily, welcome to BeiJing
通过HTML注入插入恶意的脚本,在用户浏览网页时,控制用户浏览器的一种攻击。
防御:
本质是攻击者利用用户身份操作用户账户的一种攻击方式。
防御:
为了防止内存泄露(已经不需要某块内存时这块内存还存在着),垃圾回收机制间歇的寻找不再使用的变量,释放内存。
标记清除:
垃圾收集器给所有变量加上标记,然后去掉环境中的变量以及被环境中的变量引用的变量的标记。在此之后再被加上标记的即为需要回收的变量。
引用计数:
跟踪一个值的引用次数,当该值引用次数为0时就会被回收。不安全,会引起内存泄露,主要是由于不能解决循环引用的问题。
function test() {
var a = {
};
var b = {
};
a.prop = b;
b.prop = a;
}
// 执行完test之后,a和b的引用次数都为2,无法被释放
垃圾回收时会停止响应其他的操作。优化垃圾回收机制:
function fn() {
return {
a: 'test'
}
}
var obj = fn();
setInterval(function() {
var testDom = document.getElementById("test")
if(testDom) {
testDom.innerHTML = obj;
}
}, 1000);
// 若没有回收定时器,则定时器函数中的依赖无法被回收,本例中的fn则无法被回收
function test(){
let ele = document.getElementById('id');
let id = ele.id; // 引用ele变量id
ele.onclick = function(){
console.log(id); // 引用test变量id
};
}